Ticket #7806: 7806.tplrf-r11593.diff

File 7806.tplrf-r11593.diff, 129.1 KB (added by Johannes Dollinger, 15 years ago)
  • django/templatetags/i18n.py

     
    11import re
    22
    3 from django.template import Node, Variable, VariableNode, _render_value_in_context
     3from django.template import Node, _render_value_in_context
    44from django.template import TemplateSyntaxError, TokenParser, Library
    55from django.template import TOKEN_TEXT, TOKEN_VAR
     6from django.template.utils import parse_context_bindings, parse_as, resolve_dict
     7from django.template.parser import uses_token_stream
    68from django.utils import translation
    79from django.utils.encoding import force_unicode
    810
     
    3537
    3638class TranslateNode(Node):
    3739    def __init__(self, value, noop):
    38         self.value = Variable(value)
     40        self.value = value
    3941        self.noop = noop
    4042
    4143    def render(self, context):
     
    6668        return ''.join(result), vars
    6769
    6870    def render(self, context):
    69         tmp_context = {}
    70         for var, val in self.extra_context.items():
    71             tmp_context[var] = val.render(context)
     71        tmp_context = resolve_dict(self.extra_context, context)
    7272        # Update() works like a push(), so corresponding context.pop() is at
    7373        # the end of function
    7474        context.update(tmp_context)
     
    141141        raise TemplateSyntaxError, "'get_current_language_bidi' requires 'as variable' (got %r)" % args
    142142    return GetCurrentLanguageBidiNode(args[2])
    143143
    144 def do_translate(parser, token):
     144#@uses_token_stream
     145def do_translate(parser, bits):
    145146    """
    146147    This will mark a string for translation and will
    147148    translate the string for the current language.
     
    171172    the variable ``variable``. Make sure that the string
    172173    in there is something that is in the .po file.
    173174    """
    174     class TranslateParser(TokenParser):
    175         def top(self):
    176             value = self.value()
    177             if self.more():
    178                 if self.tag() == 'noop':
    179                     noop = True
    180                 else:
    181                     raise TemplateSyntaxError, "only option for 'trans' is 'noop'"
    182             else:
    183                 noop = False
    184             return (value, noop)
    185     value, noop = TranslateParser(token.contents).top()
     175    value = bits.parse_expression(required=True)
     176    noop = bits.pop_lexeme('noop')
    186177    return TranslateNode(value, noop)
    187178
    188 def do_block_translate(parser, token):
     179#@uses_token_stream
     180def do_block_translate(parser, bits):
    189181    """
    190182    This will translate a block of text with parameters.
    191183
     
    205197
    206198    This is much like ngettext, only in template syntax.
    207199    """
    208     class BlockTranslateParser(TokenParser):
    209         def top(self):
    210             countervar = None
    211             counter = None
    212             extra_context = {}
    213             while self.more():
    214                 tag = self.tag()
    215                 if tag == 'with' or tag == 'and':
    216                     value = self.value()
    217                     if self.tag() != 'as':
    218                         raise TemplateSyntaxError, "variable bindings in 'blocktrans' must be 'with value as variable'"
    219                     extra_context[self.tag()] = VariableNode(
    220                             parser.compile_filter(value))
    221                 elif tag == 'count':
    222                     counter = parser.compile_filter(self.value())
    223                     if self.tag() != 'as':
    224                         raise TemplateSyntaxError, "counter specification in 'blocktrans' must be 'count value as variable'"
    225                     countervar = self.tag()
    226                 else:
    227                     raise TemplateSyntaxError, "unknown subtag %s for 'blocktrans' found" % tag
    228             return (countervar, counter, extra_context)
     200    countervar = None
     201    counter = None
     202    extra_context = {}
    229203
    230     countervar, counter, extra_context = BlockTranslateParser(token.contents).top()
     204    if bits.pop_lexeme('with'):
     205        extra_context = parse_context_bindings(bits)
     206    elif bits.pop_lexeme('count'):
     207        counter = bits.parse_expression(required=True)
     208        countervar = parse_as(bits, required=True)
    231209
    232210    singular = []
    233211    plural = []
     
    255233register.tag('get_available_languages', do_get_available_languages)
    256234register.tag('get_current_language', do_get_current_language)
    257235register.tag('get_current_language_bidi', do_get_current_language_bidi)
    258 register.tag('trans', do_translate)
    259 register.tag('blocktrans', do_block_translate)
     236register.tag('trans', uses_token_stream(do_translate))
     237register.tag('blocktrans', uses_token_stream(do_block_translate))
  • django/templatetags/cache.py

     
    1 from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist
    2 from django.template import resolve_variable
     1from django.template import Library, Node, TemplateSyntaxError
     2from django.template.parser import uses_token_stream
     3from django.template.expressions import LookupError
    34from django.core.cache import cache
    4 from django.utils.encoding import force_unicode
    55from django.utils.http import urlquote
    66from django.utils.hashcompat import md5_constructor
    77
    88register = Library()
    99
    1010class CacheNode(Node):
    11     def __init__(self, nodelist, expire_time_var, fragment_name, vary_on):
     11    def __init__(self, nodelist, expire_time, fragment_name, vary_on):
    1212        self.nodelist = nodelist
    13         self.expire_time_var = Variable(expire_time_var)
     13        self.expire_time = expire_time
    1414        self.fragment_name = fragment_name
    1515        self.vary_on = vary_on
    1616
    1717    def render(self, context):
    1818        try:
    19             expire_time = self.expire_time_var.resolve(context)
    20         except VariableDoesNotExist:
    21             raise TemplateSyntaxError('"cache" tag got an unknkown variable: %r' % self.expire_time_var.var)
     19            expire_time = self.expire_time.resolve(context)
     20        except LookupError:
     21            raise TemplateSyntaxError('"cache" tag got an unknkown variable: %r' % self.expire_time)
    2222        try:
    2323            expire_time = int(expire_time)
    2424        except (ValueError, TypeError):
    2525            raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
    2626        # Build a unicode key for this fragment and all vary-on's.
    27         args = md5_constructor(u':'.join([urlquote(resolve_variable(var, context)) for var in self.vary_on]))
     27        args = md5_constructor(u':'.join([urlquote(exp.resolve(context)) for exp in self.vary_on]))
    2828        cache_key = 'template.cache.%s.%s' % (self.fragment_name, args.hexdigest())
    2929        value = cache.get(cache_key)
    3030        if value is None:
     
    3232            cache.set(cache_key, value, expire_time)
    3333        return value
    3434
    35 def do_cache(parser, token):
     35#@uses_token_stream
     36def do_cache(parser, bits):
    3637    """
    3738    This will cache the contents of a template fragment for a given amount
    3839    of time.
     
    5354
    5455    Each unique set of arguments will result in a unique cache entry.
    5556    """
    56     nodelist = parser.parse(('endcache',))
    57     parser.delete_first_token()
    58     tokens = token.contents.split()
    59     if len(tokens) < 3:
    60         raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0])
    61     return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:])
     57    nodelist = parser.parse_nodelist('endcache')
     58    expire_time = bits.parse_expression(required=True)
     59    fragment_name = bits.pop_name(required=True)
     60    vary_on = bits.parse_expression_list()
     61    return CacheNode(nodelist, expire_time, fragment_name, vary_on)
    6262
    63 register.tag('cache', do_cache)
     63register.tag('cache', uses_token_stream(do_cache))
  • django/contrib/comments/templatetags/comments.py

     
    11from django import template
     2from django.template.parser import uses_token_stream, TokenSyntaxError
    23from django.template.loader import render_to_string
     4from django.template.utils import parse_as
    35from django.conf import settings
    46from django.contrib.contenttypes.models import ContentType
    57from django.contrib import comments
     
    1315    Looks a bit strange, but the subclasses below should make this a bit more
    1416    obvious.
    1517    """
    16 
    1718    #@classmethod
    18     def handle_token(cls, parser, token):
     19    def handle_token(cls, parser, bits):
    1920        """Class method to parse get_comment_list/count/form and return a Node."""
    20         tokens = token.contents.split()
    21         if tokens[1] != 'for':
    22             raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0])
    23 
    24         # {% get_whatever for obj as varname %}
    25         if len(tokens) == 5:
    26             if tokens[3] != 'as':
    27                 raise template.TemplateSyntaxError("Third argument in %r must be 'as'" % tokens[0])
     21        bits.pop_lexeme('for', required=True)
     22        try:
     23            # {% get_whatever for app.model pk as varname %}
     24            app_and_model = bits.parse_string(bare=True)
     25            if bits.pop_lexeme('as'):
     26                bits.pushback() # pushback "as"
     27                bits.pushback() # pushback app_and_model
     28                raise TokenSyntaxError
     29            pk = bits.parse_expression(required=True)
     30            varname = parse_as(bits, required=True)
    2831            return cls(
    29                 object_expr = parser.compile_filter(tokens[2]),
    30                 as_varname = tokens[4],
    31             )
     32                ctype=BaseCommentNode.lookup_content_type(app_and_model, bits.name),
     33                object_pk_expr = pk,
     34                as_varname = varname
     35            )           
     36        except TokenSyntaxError:       
     37            # {% get_whatever for obj as varname %}
     38            obj = bits.parse_expression(required=True)
     39            varname = parse_as(bits, required=True)
     40            return cls(object_expr=obj, as_varname = varname)
    3241
    33         # {% get_whatever for app.model pk as varname %}
    34         elif len(tokens) == 6:
    35             if tokens[4] != 'as':
    36                 raise template.TemplateSyntaxError("Fourth argument in %r must be 'as'" % tokens[0])
    37             return cls(
    38                 ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]),
    39                 object_pk_expr = parser.compile_filter(tokens[3]),
    40                 as_varname = tokens[5]
    41             )
    42 
    43         else:
    44             raise template.TemplateSyntaxError("%r tag requires 4 or 5 arguments" % tokens[0])
    45 
    4642    handle_token = classmethod(handle_token)
    4743
    4844    #@staticmethod
     
    136132    """Render the comment form directly"""
    137133
    138134    #@classmethod
    139     def handle_token(cls, parser, token):
     135    def handle_token(cls, parser, bits):
    140136        """Class method to parse render_comment_form and return a Node."""
    141         tokens = token.contents.split()
    142         if tokens[1] != 'for':
    143             raise template.TemplateSyntaxError("Second argument in %r tag must be 'for'" % tokens[0])
    144 
    145         # {% render_comment_form for obj %}
    146         if len(tokens) == 3:
    147             return cls(object_expr=parser.compile_filter(tokens[2]))
    148 
    149         # {% render_comment_form for app.models pk %}
    150         elif len(tokens) == 4:
    151             return cls(
    152                 ctype = BaseCommentNode.lookup_content_type(tokens[2], tokens[0]),
    153                 object_pk_expr = parser.compile_filter(tokens[3])
    154             )
     137        bits.pop_lexeme('for', required=True)
     138        try:
     139            # {% render_comment_form for app.models pk %}
     140            app_and_model = bits.parse_string(bare=True)
     141            try:
     142                pk = bits.parse_expression()
     143                return cls(
     144                    ctype = BaseCommentNode.lookup_content_type(app_and_model, bits.name),
     145                    object_pk_expr = pk
     146                )               
     147            except TokenSyntaxError:
     148                bits.pushback()
     149                raise
     150        except TokenSyntaxError:
     151            # {% render_comment_form for obj %}           
     152            return cls(object_expr=bits.parse_expression(required=True))
    155153    handle_token = classmethod(handle_token)
    156154
    157155    def render(self, context):
     
    174172# wrapper function that just exists to hold the docstring.
    175173
    176174#@register.tag
     175#@uses_token_stream
    177176def get_comment_count(parser, token):
    178177    """
    179178    Gets the comment count for the given params and populates the template
     
    195194    return CommentCountNode.handle_token(parser, token)
    196195
    197196#@register.tag
     197#@uses_token_stream
    198198def get_comment_list(parser, token):
    199199    """
    200200    Gets the list of comments for the given params and populates the template
     
    217217    return CommentListNode.handle_token(parser, token)
    218218
    219219#@register.tag
     220#@uses_token_stream
    220221def get_comment_form(parser, token):
    221222    """
    222223    Get a (new) form object to post a new comment.
     
    252253    """
    253254    return comments.get_form_target()
    254255
    255 register.tag(get_comment_count)
    256 register.tag(get_comment_list)
    257 register.tag(get_comment_form)
    258 register.tag(render_comment_form)
     256register.tag(uses_token_stream(get_comment_count))
     257register.tag(uses_token_stream(get_comment_list))
     258register.tag(uses_token_stream(get_comment_form))
     259register.tag(uses_token_stream(render_comment_form))
    259260register.simple_tag(comment_form_target)
  • django/template/nodes.py

     
     1from django.utils.encoding import force_unicode
     2from django.utils.safestring import SafeData, EscapeData, mark_safe
     3from django.utils.html import escape
     4from django.template.expressions import LookupError
     5from django.conf import settings
     6
     7class Node(object):
     8    # Set this to True for nodes that must be first in the template (although
     9    # they can be preceded by text nodes.
     10    must_be_first = False
     11
     12    def render(self, context):
     13        "Return the node rendered as a string"
     14        pass
     15       
     16    def __repr__(self):
     17        return "<%s>" % self.__class__.__name__
     18
     19    def __iter__(self):
     20        yield self
     21
     22    def get_nodes_by_type(self, nodetype):
     23        "Return a list of all nodes (within this node and its nodelist) of the given type"
     24        nodes = []
     25        if isinstance(self, nodetype):
     26            nodes.append(self)
     27        if hasattr(self, 'nodelist'):
     28            nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
     29        return nodes
     30
     31class NodeList(list):
     32    # Set to True the first time a non-TextNode is inserted by
     33    # extend_nodelist().
     34    contains_nontext = False
     35
     36    def render(self, context):
     37        bits = []
     38        for node in self:
     39            if isinstance(node, Node):
     40                bits.append(self.render_node(node, context))
     41            else:
     42                bits.append(node)
     43        return mark_safe(''.join([force_unicode(b) for b in bits]))
     44
     45    def get_nodes_by_type(self, nodetype):
     46        "Return a list of all nodes of the given type"
     47        nodes = []
     48        for node in self:
     49            nodes.extend(node.get_nodes_by_type(nodetype))
     50        return nodes
     51
     52    def render_node(self, node, context):
     53        return node.render(context)
     54
     55class TextNode(Node):
     56    def __init__(self, s):
     57        self.s = s
     58
     59    def __repr__(self):
     60        return "<Text Node: '%s'>" % self.s[:25]
     61
     62    def render(self, context):
     63        return self.s
     64
     65class ExpressionNode(Node):
     66    def __init__(self, expression):
     67        self.expression = expression
     68
     69    def __repr__(self):
     70        return "<Variable Node: %s>" % self.expression
     71
     72    def render(self, context):
     73        try:
     74            output = force_unicode(self.expression.resolve(context))
     75        except LookupError:
     76            if settings.TEMPLATE_STRING_IF_INVALID:
     77                from django.template import invalid_var_format_string
     78                if invalid_var_format_string is None:
     79                    invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
     80                if invalid_var_format_string:
     81                    return settings.TEMPLATE_STRING_IF_INVALID % self.expression
     82                return settings.TEMPLATE_STRING_IF_INVALID
     83            else:
     84                return u''
     85        except UnicodeDecodeError:
     86            # Unicode conversion can fail sometimes for reasons out of our
     87            # control (e.g. exception rendering). In that case, we fail quietly.
     88            return u''       
     89        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
     90            output = escape(output)
     91        return output
  • django/template/parser.py

     
     1import re
     2from inspect import getargspec
     3from django.conf import settings
     4from django.utils.functional import wraps
     5from django.utils.translation import ugettext
     6from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
     7from django.utils.html import escape
     8from django.utils.encoding import smart_unicode, force_unicode, smart_str
     9from django.utils.text import smart_split
     10from django.template.nodes import Node, ExpressionNode, NodeList, TextNode
     11from django.template.expressions import LookupError, Literal, FilterExpression, Lookup
     12
     13
     14# what to report as the origin for templates that come from non-loader sources
     15# (e.g. strings)
     16UNKNOWN_SOURCE="&lt;unknown source&gt;"
     17
     18TOKEN_TEXT = 0
     19TOKEN_VAR = 1
     20TOKEN_BLOCK = 2
     21TOKEN_COMMENT = 3
     22
     23# template syntax constants
     24FILTER_SEPARATOR = '|'
     25FILTER_ARGUMENT_SEPARATOR = ':'
     26VARIABLE_ATTRIBUTE_SEPARATOR = '.'
     27BLOCK_TAG_START = '{%'
     28BLOCK_TAG_END = '%}'
     29VARIABLE_TAG_START = '{{'
     30VARIABLE_TAG_END = '}}'
     31COMMENT_TAG_START = '{#'
     32COMMENT_TAG_END = '#}'
     33SINGLE_BRACE_START = '{'
     34SINGLE_BRACE_END = '}'
     35
     36ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
     37
     38# match a variable or block tag and capture the entire tag, including start/end delimiters
     39tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
     40                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
     41                                          re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
     42
     43def _render_value_in_context(value, context):
     44    """
     45    Converts any value to a string to become part of a rendered template. This
     46    means escaping, if required, and conversion to a unicode object. If value
     47    is a string, it is expected to have already been translated.
     48    """
     49    value = force_unicode(value)
     50    if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData):
     51        return escape(value)
     52    else:
     53        return value
     54       
     55class Origin(object):
     56    def __init__(self, name):
     57        self.name = name
     58
     59    def reload(self):
     60        raise NotImplementedError
     61
     62    def __str__(self):
     63        return self.name
     64
     65class StringOrigin(Origin):
     66    def __init__(self, source):
     67        super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
     68        self.source = source
     69
     70    def reload(self):
     71        return self.source
     72
     73class TemplateSyntaxError(Exception):
     74    def __str__(self):
     75        try:
     76            import cStringIO as StringIO
     77        except ImportError:
     78            import StringIO
     79        output = StringIO.StringIO()
     80        output.write(Exception.__str__(self))
     81        # Check if we wrapped an exception and print that too.
     82        if hasattr(self, 'exc_info'):
     83            import traceback
     84            output.write('\n\nOriginal ')
     85            e = self.exc_info
     86            traceback.print_exception(e[0], e[1], e[2], 500, output)
     87        return output.getvalue()
     88       
     89class TemplateEncodingError(Exception):
     90    pass
     91       
     92class Template(object):
     93    def __init__(self, template_string, origin=None, name='<Unknown Template>'):
     94        try:
     95            template_string = smart_unicode(template_string)
     96        except UnicodeDecodeError:
     97            raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
     98        if settings.TEMPLATE_DEBUG and origin is None:
     99            origin = StringOrigin(template_string)
     100        self.nodelist = compile_string(template_string, origin)
     101        self.name = name
     102
     103    def __iter__(self):
     104        for node in self.nodelist:
     105            for subnode in node:
     106                yield subnode
     107
     108    def render(self, context):
     109        "Display stage -- can be called many times"
     110        return self.nodelist.render(context)
     111
     112def compile_string(template_string, origin):
     113    "Compiles template_string into NodeList ready for rendering"
     114    if settings.TEMPLATE_DEBUG:
     115        from debug import DebugLexer, DebugParser
     116        lexer_class, parser_class = DebugLexer, DebugParser
     117    else:
     118        lexer_class, parser_class = Lexer, Parser
     119    lexer = lexer_class(template_string, origin)
     120    parser = parser_class(lexer.tokenize())
     121    return parser.parse()
     122
     123class Token(object):
     124    def __init__(self, token_type, contents):
     125        # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
     126        self.token_type, self.contents = token_type, contents
     127
     128    def __str__(self):
     129        return '<%s token: "%s...">' % \
     130            ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
     131            self.contents[:20].replace('\n', ''))
     132
     133    def split_contents(self):
     134        split = []
     135        bits = iter(smart_split(self.contents))
     136        for bit in bits:
     137            # Handle translation-marked template pieces
     138            if bit.startswith('_("') or bit.startswith("_('"):
     139                sentinal = bit[2] + ')'
     140                trans_bit = [bit]
     141                while not bit.endswith(sentinal):
     142                    bit = bits.next()
     143                    trans_bit.append(bit)
     144                bit = ' '.join(trans_bit)
     145            split.append(bit)
     146        return split
     147
     148class Lexer(object):
     149    def __init__(self, template_string, origin):
     150        self.template_string = template_string
     151        self.origin = origin
     152
     153    def tokenize(self):
     154        "Return a list of tokens from a given template_string."
     155        in_tag = False
     156        result = []
     157        for bit in tag_re.split(self.template_string):
     158            if bit:
     159                result.append(self.create_token(bit, in_tag))
     160            in_tag = not in_tag
     161        return result
     162
     163    def create_token(self, token_string, in_tag):
     164        """
     165        Convert the given token string into a new Token object and return it.
     166        If in_tag is True, we are processing something that matched a tag,
     167        otherwise it should be treated as a literal string.
     168        """
     169        if in_tag:
     170            if token_string.startswith(VARIABLE_TAG_START):
     171                token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
     172            elif token_string.startswith(BLOCK_TAG_START):
     173                token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
     174            elif token_string.startswith(COMMENT_TAG_START):
     175                token = Token(TOKEN_COMMENT, '')
     176        else:
     177            token = Token(TOKEN_TEXT, token_string)
     178        return token
     179
     180class Parser(object):
     181    def __init__(self, tokens):
     182        self.tokens = tokens
     183        self.tags = {}
     184        self.filters = {}
     185        from django.template.library import get_builtins
     186        for lib in get_builtins():
     187            self.add_library(lib)
     188
     189    def parse(self, parse_until=None):
     190        if parse_until is None: parse_until = []
     191        nodelist = self.create_nodelist()
     192        while self.tokens:
     193            token = self.next_token()
     194            if token.token_type == TOKEN_TEXT:
     195                self.extend_nodelist(nodelist, TextNode(token.contents), token)
     196            elif token.token_type == TOKEN_VAR:
     197                if not token.contents:
     198                    self.empty_variable(token)
     199                filter_expression = self.compile_filter(token.contents)
     200                var_node = self.create_variable_node(filter_expression)
     201                self.extend_nodelist(nodelist, var_node,token)
     202            elif token.token_type == TOKEN_BLOCK:
     203                if token.contents in parse_until:
     204                    # put token back on token list so calling code knows why it terminated
     205                    self.prepend_token(token)
     206                    return nodelist
     207                try:
     208                    command = token.contents.split()[0]
     209                except IndexError:
     210                    self.empty_block_tag(token)
     211                # execute callback function for this tag and append resulting node
     212                self.enter_command(command, token)
     213                try:
     214                    compile_func = self.tags[command]
     215                except KeyError:
     216                    self.invalid_block_tag(token, command)
     217                try:
     218                    compiled_result = compile_func(self, token)
     219                except TemplateSyntaxError, e:
     220                    if not self.compile_function_error(token, e):
     221                        raise
     222                self.extend_nodelist(nodelist, compiled_result, token)
     223                self.exit_command()
     224        if parse_until:
     225            self.unclosed_block_tag(parse_until)
     226        return nodelist
     227       
     228    def parse_nodelist(self, until):
     229        nodelist = self.parse((until,))
     230        self.delete_first_token()
     231        return nodelist
     232
     233    def skip_past(self, endtag):
     234        while self.tokens:
     235            token = self.next_token()
     236            if token.token_type == TOKEN_BLOCK and token.contents == endtag:
     237                return
     238        self.unclosed_block_tag([endtag])
     239
     240    def create_variable_node(self, expression):
     241        return ExpressionNode(expression)
     242
     243    def create_nodelist(self):
     244        return NodeList()
     245
     246    def extend_nodelist(self, nodelist, node, token):
     247        if node.must_be_first and nodelist:
     248            try:
     249                if nodelist.contains_nontext:
     250                    raise AttributeError
     251            except AttributeError:
     252                raise TemplateSyntaxError("%r must be the first tag in the template." % node)
     253        if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
     254            nodelist.contains_nontext = True
     255        nodelist.append(node)
     256
     257    def enter_command(self, command, token):
     258        pass
     259
     260    def exit_command(self):
     261        pass
     262
     263    def error(self, token, msg):
     264        return TemplateSyntaxError(msg)
     265
     266    def empty_variable(self, token):
     267        raise self.error(token, "Empty variable tag")
     268
     269    def empty_block_tag(self, token):
     270        raise self.error(token, "Empty block tag")
     271
     272    def invalid_block_tag(self, token, command):
     273        raise self.error(token, "Invalid block tag: '%s'" % command)
     274
     275    def unclosed_block_tag(self, parse_until):
     276        raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
     277
     278    def compile_function_error(self, token, e):
     279        pass
     280
     281    def next_token(self):
     282        return self.tokens.pop(0)
     283
     284    def prepend_token(self, token):
     285        self.tokens.insert(0, token)
     286
     287    def delete_first_token(self):
     288        del self.tokens[0]
     289
     290    def add_library(self, lib):
     291        self.tags.update(lib.tags)
     292        self.filters.update(lib.filters)
     293
     294    def token_stream(self, token):
     295        return TokenStream(self, token)
     296
     297    def compile_filter(self, token):
     298        stream = self.token_stream(token)
     299        expr = stream.parse_expression(required=True)
     300        if not stream.consumed():
     301            raise TemplateSyntaxError("Invalid filter expression")
     302        return expr
     303
     304    def find_filter(self, filter_name):
     305        if filter_name in self.filters:
     306            return self.filters[filter_name]
     307        else:
     308            raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
     309
     310def filter_args_check(name, func, provided):
     311    provided = list(provided)
     312    plen = len(provided)
     313    # Check to see if a decorator is providing the real function.
     314    func = getattr(func, '_decorated_function', func)
     315    args, varargs, varkw, defaults = getargspec(func)
     316   
     317    if plen + 1 == len(args) or (defaults and plen + 1 <= len(args) + len(defaults)):
     318        return True
     319   
     320    # First argument is filter input.
     321    args.pop(0)
     322    if defaults:
     323        nondefs = args[:-len(defaults)]
     324    else:
     325        nondefs = args
     326    # Args without defaults must be provided.
     327    try:
     328        for arg in nondefs:
     329            provided.pop(0)
     330    except IndexError:
     331        # Not enough
     332        raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
     333
     334    # Defaults can be overridden.
     335    defaults = defaults and list(defaults) or []
     336    try:
     337        for parg in provided:
     338            defaults.pop(0)
     339    except IndexError:
     340        # Too many.
     341        raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
     342
     343    return True
     344
     345
     346def parse_lookup(lookup):
     347    return Lookup(tuple(lookup.split(VARIABLE_ATTRIBUTE_SEPARATOR)), var=lookup)
     348
     349EOT = '<EOT>'
     350punctuation_chars = r':|=,;<>!?%&@"\'/()\[\]{}`*+'
     351bit_re = re.compile(r"""
     352    (?P<string_literal>"(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)')
     353    |(?P<numeric_literal>[+-]?\.?\d[\d\.e]*)
     354    |(?P<char>[%s]) # punctuation
     355    |(?P<name>[^\s%s]+) # keyword or variable
     356""" % ((punctuation_chars,) * 2), re.VERBOSE)
     357STRING_LITERAL = "string_literal" #1
     358NUMERIC_LITERAL = "numeric_literal" #2
     359CHAR = "char" #3
     360NAME = "name" #4
     361
     362def _unescape_string_literal(literal):
     363    q = literal[0]
     364    return literal.replace(r'\%s' % q, q).replace(r'\\', '')[1:-1]
     365
     366class TokenSyntaxError(Exception):
     367    pass
     368
     369def token_stream_parser(func):
     370    def wrapper(self, required=False, *args, **kwargs):
     371        mark = self.offset
     372        try:
     373            return func(self, *args, **kwargs)
     374        except TokenSyntaxError:
     375            if required:
     376                #FIXME: hack
     377                self.expected("<%s>" % ' '.join(func.__name__.split('_')[1:]))
     378            self.offset = mark
     379            raise
     380    return wraps(func)(wrapper)
     381
     382class TokenStream(object):
     383    def __init__(self, parser, source):
     384        self.parser = parser
     385        self.source = source
     386        self.offset = 0
     387        self.name = None
     388        self.token = None
     389        if isinstance(source, Token):
     390            bits = source.contents.split(None, 1)
     391            self.source = len(bits) == 2 and bits[1] or ''
     392            self.token = source
     393            self.name = bits[0]
     394        self.tokens = [(bit.lastgroup, bit.group(0)) for bit in bit_re.finditer(self.source)]
     395
     396    def consumed(self):
     397        return self.offset == len(self.tokens)
     398
     399    def pop(self):
     400        if self.offset == len(self.tokens):
     401            raise TokenSyntaxError
     402        next = self.tokens[self.offset]
     403        self.offset += 1
     404        return next
     405
     406    def pop_lexeme(self, match, required=False):
     407        if self.offset == len(self.tokens):
     408            if required:
     409                self.expected(match)
     410            return False
     411        tokentype, lexeme = self.tokens[self.offset]
     412        if lexeme == match:
     413            self.offset += 1
     414            return True
     415        if required:
     416            self.expected(match)
     417        return False
     418
     419    def pop_name(self, required=False):
     420        if self.offset == len(self.tokens):
     421            if required:
     422                self.expected('<name>')       
     423            return None
     424        tokentype, lexeme = self.tokens[self.offset]
     425        if tokentype == NAME:
     426            self.offset += 1
     427            return lexeme
     428        if required:
     429            self.expected('<name>')
     430        return None
     431
     432    def pushback(self):
     433        self.offset -= 1
     434
     435    def syntax_error(self, msg):
     436        if self.name:
     437            msg = u"{%% %s %%} %s" % (self.name, msg)
     438        raise TemplateSyntaxError(msg)
     439
     440    def expected(self, what):
     441        if self.consumed():
     442            found = EOT
     443        else:
     444            found = "<%s> %s" % self.tokens[self.offset]
     445        self.syntax_error("expected %s, found %s" % (what, smart_str(found, encoding='ascii', errors='backslashreplace')))
     446
     447    #@token_stream_parser
     448    def parse_string(self, bare=False):
     449        tokentype, lexeme = self.pop()
     450        if tokentype == STRING_LITERAL:
     451            return _unescape_string_literal(lexeme)
     452        if bare and tokentype == NAME:
     453            return lexeme
     454        raise TokenSyntaxError
     455    parse_string = token_stream_parser(parse_string)
     456
     457    #@token_stream_parser
     458    def parse_int(self):
     459        token_type, lexeme = self.pop()
     460        if token_type == NUMERIC_LITERAL:
     461            try:
     462                return int(lexeme)
     463            except ValueError:
     464                pass
     465        raise TokenSyntaxError
     466    parse_int = token_stream_parser(parse_int)
     467
     468    #@token_stream_parser
     469    def parse_value(self):
     470        translate = False
     471        if self.pop_lexeme('_'):
     472            if not self.pop_lexeme('('):
     473                raise TokenSyntaxError
     474            translate = True
     475
     476        tokentype, lexeme = self.pop()
     477        if tokentype == NAME:
     478            if lexeme.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or lexeme[0] == '_':
     479                raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % lexeme)
     480            value = parse_lookup(lexeme)
     481        elif tokentype == STRING_LITERAL:
     482            value = Literal(mark_safe(_unescape_string_literal(lexeme)))
     483        elif tokentype == NUMERIC_LITERAL:
     484            try:
     485                value = float(lexeme)
     486            except ValueError:
     487                raise TokenSyntaxError
     488            if '.' not in lexeme and 'e' not in lexeme.lower():
     489                value = int(value)
     490            # This is a backwards compatibility hack: Literals with a trailing dot
     491            # have been treated as an invalid variable before, thus resolved as None.
     492            if lexeme.endswith('.'):
     493                value = None
     494            value = Literal(value)
     495        elif tokentype == CHAR:
     496            raise TokenSyntaxError
     497
     498        if translate:
     499            if not self.pop_lexeme(')'):
     500                raise TokenSyntaxError
     501            # Don't pass the empty string to gettext, because the empty
     502            # string translates to meta information
     503            value = FilterExpression(value, [(lambda x: x and ugettext(x) or u'', ())])
     504        return value
     505    parse_value = token_stream_parser(parse_value)
     506
     507    #@token_stream_parser
     508    def parse_filter(self):
     509        if not self.pop_lexeme('|'):
     510            raise TokenSyntaxError
     511        name = self.pop_name()
     512        if not name:
     513            raise TokenSyntaxError
     514        args = []
     515        if self.pop_lexeme(':'):
     516            args.append(self.parse_value())
     517        func = self.parser.find_filter(name)
     518        filter_args_check(name, func, args)
     519        return func, args
     520    parse_filter = token_stream_parser(parse_filter)
     521
     522    #@token_stream_parser
     523    def parse_expression(self):
     524        var = self.parse_value()
     525        filters = []
     526        try:
     527            while True:
     528                filters.append(self.parse_filter())
     529        except TokenSyntaxError:
     530            pass
     531        if filters:
     532            return FilterExpression(var, filters)
     533        return var
     534    parse_expression = token_stream_parser(parse_expression)
     535
     536    #@token_stream_parser
     537    def parse_expression_list(self, minimum=0, maximum=None, count=None):
     538        expressions = []
     539        if count:
     540            minimum = count
     541            maximum = count
     542        try:
     543            while True:
     544                if len(expressions) == maximum:
     545                    break           
     546                expressions.append(self.parse_expression())
     547        except TokenSyntaxError:
     548            pass
     549        if len(expressions) < minimum:
     550            self.expected("expression")
     551        return expressions
     552    parse_expression_list = token_stream_parser(parse_expression_list)
     553
     554def uses_token_stream(func):
     555    def decorator(parser, token):
     556        bits = parser.token_stream(token)
     557        result = func(parser, bits)
     558        if bits.offset != len(bits.tokens):
     559            bits.expected(EOT)
     560        return result   
     561    return wraps(func)(decorator)
  • django/template/defaultfilters.py

     
    1313except ImportError:
    1414    from django.utils.functional import wraps  # Python 2.3, 2.4 fallback.
    1515
    16 from django.template import Variable, Library
     16from django.template import Library
     17from django.template.parser import parse_lookup
    1718from django.conf import settings
    1819from django.utils.translation import ugettext, ungettext
    1920from django.utils.encoding import force_unicode, iri_to_uri
     
    467468    Takes a list of dicts, returns that list sorted by the property given in
    468469    the argument.
    469470    """
    470     var_resolve = Variable(arg).resolve
    471     decorated = [(var_resolve(item), item) for item in value]
     471    lookup = parse_lookup(arg)
     472    decorated = [(lookup.resolve(item), item) for item in value]
    472473    decorated.sort()
    473474    return [item[1] for item in decorated]
    474475dictsort.is_safe = False
     
    478479    Takes a list of dicts, returns that list sorted in reverse order by the
    479480    property given in the argument.
    480481    """
    481     var_resolve = Variable(arg).resolve
    482     decorated = [(var_resolve(item), item) for item in value]
     482    lookup = parse_lookup(arg)
     483    decorated = [(lookup.resolve(item), item) for item in value]
    483484    decorated.sort()
    484485    decorated.reverse()
    485486    return [item[1] for item in decorated]
  • django/template/__init__.py

     
    4848>>> t.render(c)
    4949u'<html></html>'
    5050"""
    51 import re
    52 from inspect import getargspec
    53 
    54 from django.conf import settings
    5551from django.template.context import Context, RequestContext, ContextPopException
    56 from django.utils.importlib import import_module
    57 from django.utils.itercompat import is_iterable
    58 from django.utils.functional import curry, Promise
    59 from django.utils.text import smart_split, unescape_string_literal
    60 from django.utils.encoding import smart_unicode, force_unicode, smart_str
    61 from django.utils.translation import ugettext as _
    62 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
    63 from django.utils.html import escape
     52from django.template.nodes import Node, NodeList, TextNode, ExpressionNode
     53from django.template.parser import Parser, Lexer, Token, Template, Origin, StringOrigin, TemplateSyntaxError, TemplateEncodingError, TokenStream, _render_value_in_context, compile_string
     54from django.template.parser import (BLOCK_TAG_START, BLOCK_TAG_END,
     55    VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END,
     56    COMMENT_TAG_START, COMMENT_TAG_END, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK, TOKEN_COMMENT)
     57from django.template.loader import TemplateDoesNotExist
     58from django.template.library import Library, InvalidTemplateLibrary, get_library, add_to_builtins, libraries, builtins
     59from django.template.compat import Variable, resolve_variable, TokenParser, VariableDoesNotExist, VariableNode
    6460
    65 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
     61__all__ = ('Template', 'Context', 'RequestContext', 'compile_string', 'Library', 'Parser', 'TemplateDoesNotExist')
    6662
    67 TOKEN_TEXT = 0
    68 TOKEN_VAR = 1
    69 TOKEN_BLOCK = 2
    70 TOKEN_COMMENT = 3
    71 
    72 # template syntax constants
    73 FILTER_SEPARATOR = '|'
    74 FILTER_ARGUMENT_SEPARATOR = ':'
    75 VARIABLE_ATTRIBUTE_SEPARATOR = '.'
    76 BLOCK_TAG_START = '{%'
    77 BLOCK_TAG_END = '%}'
    78 VARIABLE_TAG_START = '{{'
    79 VARIABLE_TAG_END = '}}'
    80 COMMENT_TAG_START = '{#'
    81 COMMENT_TAG_END = '#}'
    82 SINGLE_BRACE_START = '{'
    83 SINGLE_BRACE_END = '}'
    84 
    85 ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
    86 
    87 # what to report as the origin for templates that come from non-loader sources
    88 # (e.g. strings)
    89 UNKNOWN_SOURCE="&lt;unknown source&gt;"
    90 
    91 # match a variable or block tag and capture the entire tag, including start/end delimiters
    92 tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
    93                                           re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
    94                                           re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
    95 
    96 # global dictionary of libraries that have been loaded using get_library
    97 libraries = {}
    98 # global list of libraries to load by default for a new parser
    99 builtins = []
    100 
    10163# True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means
    10264# uninitialised.
    10365invalid_var_format_string = None
    104 
    105 class TemplateSyntaxError(Exception):
    106     def __str__(self):
    107         try:
    108             import cStringIO as StringIO
    109         except ImportError:
    110             import StringIO
    111         output = StringIO.StringIO()
    112         output.write(Exception.__str__(self))
    113         # Check if we wrapped an exception and print that too.
    114         if hasattr(self, 'exc_info'):
    115             import traceback
    116             output.write('\n\nOriginal ')
    117             e = self.exc_info
    118             traceback.print_exception(e[0], e[1], e[2], 500, output)
    119         return output.getvalue()
    120 
    121 class TemplateDoesNotExist(Exception):
    122     pass
    123 
    124 class TemplateEncodingError(Exception):
    125     pass
    126 
    127 class VariableDoesNotExist(Exception):
    128 
    129     def __init__(self, msg, params=()):
    130         self.msg = msg
    131         self.params = params
    132 
    133     def __str__(self):
    134         return unicode(self).encode('utf-8')
    135 
    136     def __unicode__(self):
    137         return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
    138 
    139 class InvalidTemplateLibrary(Exception):
    140     pass
    141 
    142 class Origin(object):
    143     def __init__(self, name):
    144         self.name = name
    145 
    146     def reload(self):
    147         raise NotImplementedError
    148 
    149     def __str__(self):
    150         return self.name
    151 
    152 class StringOrigin(Origin):
    153     def __init__(self, source):
    154         super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
    155         self.source = source
    156 
    157     def reload(self):
    158         return self.source
    159 
    160 class Template(object):
    161     def __init__(self, template_string, origin=None, name='<Unknown Template>'):
    162         try:
    163             template_string = smart_unicode(template_string)
    164         except UnicodeDecodeError:
    165             raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")
    166         if settings.TEMPLATE_DEBUG and origin is None:
    167             origin = StringOrigin(template_string)
    168         self.nodelist = compile_string(template_string, origin)
    169         self.name = name
    170 
    171     def __iter__(self):
    172         for node in self.nodelist:
    173             for subnode in node:
    174                 yield subnode
    175 
    176     def render(self, context):
    177         "Display stage -- can be called many times"
    178         return self.nodelist.render(context)
    179 
    180 def compile_string(template_string, origin):
    181     "Compiles template_string into NodeList ready for rendering"
    182     if settings.TEMPLATE_DEBUG:
    183         from debug import DebugLexer, DebugParser
    184         lexer_class, parser_class = DebugLexer, DebugParser
    185     else:
    186         lexer_class, parser_class = Lexer, Parser
    187     lexer = lexer_class(template_string, origin)
    188     parser = parser_class(lexer.tokenize())
    189     return parser.parse()
    190 
    191 class Token(object):
    192     def __init__(self, token_type, contents):
    193         # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.
    194         self.token_type, self.contents = token_type, contents
    195 
    196     def __str__(self):
    197         return '<%s token: "%s...">' % \
    198             ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
    199             self.contents[:20].replace('\n', ''))
    200 
    201     def split_contents(self):
    202         split = []
    203         bits = iter(smart_split(self.contents))
    204         for bit in bits:
    205             # Handle translation-marked template pieces
    206             if bit.startswith('_("') or bit.startswith("_('"):
    207                 sentinal = bit[2] + ')'
    208                 trans_bit = [bit]
    209                 while not bit.endswith(sentinal):
    210                     bit = bits.next()
    211                     trans_bit.append(bit)
    212                 bit = ' '.join(trans_bit)
    213             split.append(bit)
    214         return split
    215 
    216 class Lexer(object):
    217     def __init__(self, template_string, origin):
    218         self.template_string = template_string
    219         self.origin = origin
    220 
    221     def tokenize(self):
    222         "Return a list of tokens from a given template_string."
    223         in_tag = False
    224         result = []
    225         for bit in tag_re.split(self.template_string):
    226             if bit:
    227                 result.append(self.create_token(bit, in_tag))
    228             in_tag = not in_tag
    229         return result
    230 
    231     def create_token(self, token_string, in_tag):
    232         """
    233         Convert the given token string into a new Token object and return it.
    234         If in_tag is True, we are processing something that matched a tag,
    235         otherwise it should be treated as a literal string.
    236         """
    237         if in_tag:
    238             if token_string.startswith(VARIABLE_TAG_START):
    239                 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
    240             elif token_string.startswith(BLOCK_TAG_START):
    241                 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
    242             elif token_string.startswith(COMMENT_TAG_START):
    243                 token = Token(TOKEN_COMMENT, '')
    244         else:
    245             token = Token(TOKEN_TEXT, token_string)
    246         return token
    247 
    248 class Parser(object):
    249     def __init__(self, tokens):
    250         self.tokens = tokens
    251         self.tags = {}
    252         self.filters = {}
    253         for lib in builtins:
    254             self.add_library(lib)
    255 
    256     def parse(self, parse_until=None):
    257         if parse_until is None: parse_until = []
    258         nodelist = self.create_nodelist()
    259         while self.tokens:
    260             token = self.next_token()
    261             if token.token_type == TOKEN_TEXT:
    262                 self.extend_nodelist(nodelist, TextNode(token.contents), token)
    263             elif token.token_type == TOKEN_VAR:
    264                 if not token.contents:
    265                     self.empty_variable(token)
    266                 filter_expression = self.compile_filter(token.contents)
    267                 var_node = self.create_variable_node(filter_expression)
    268                 self.extend_nodelist(nodelist, var_node,token)
    269             elif token.token_type == TOKEN_BLOCK:
    270                 if token.contents in parse_until:
    271                     # put token back on token list so calling code knows why it terminated
    272                     self.prepend_token(token)
    273                     return nodelist
    274                 try:
    275                     command = token.contents.split()[0]
    276                 except IndexError:
    277                     self.empty_block_tag(token)
    278                 # execute callback function for this tag and append resulting node
    279                 self.enter_command(command, token)
    280                 try:
    281                     compile_func = self.tags[command]
    282                 except KeyError:
    283                     self.invalid_block_tag(token, command)
    284                 try:
    285                     compiled_result = compile_func(self, token)
    286                 except TemplateSyntaxError, e:
    287                     if not self.compile_function_error(token, e):
    288                         raise
    289                 self.extend_nodelist(nodelist, compiled_result, token)
    290                 self.exit_command()
    291         if parse_until:
    292             self.unclosed_block_tag(parse_until)
    293         return nodelist
    294 
    295     def skip_past(self, endtag):
    296         while self.tokens:
    297             token = self.next_token()
    298             if token.token_type == TOKEN_BLOCK and token.contents == endtag:
    299                 return
    300         self.unclosed_block_tag([endtag])
    301 
    302     def create_variable_node(self, filter_expression):
    303         return VariableNode(filter_expression)
    304 
    305     def create_nodelist(self):
    306         return NodeList()
    307 
    308     def extend_nodelist(self, nodelist, node, token):
    309         if node.must_be_first and nodelist:
    310             try:
    311                 if nodelist.contains_nontext:
    312                     raise AttributeError
    313             except AttributeError:
    314                 raise TemplateSyntaxError("%r must be the first tag in the template." % node)
    315         if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):
    316             nodelist.contains_nontext = True
    317         nodelist.append(node)
    318 
    319     def enter_command(self, command, token):
    320         pass
    321 
    322     def exit_command(self):
    323         pass
    324 
    325     def error(self, token, msg):
    326         return TemplateSyntaxError(msg)
    327 
    328     def empty_variable(self, token):
    329         raise self.error(token, "Empty variable tag")
    330 
    331     def empty_block_tag(self, token):
    332         raise self.error(token, "Empty block tag")
    333 
    334     def invalid_block_tag(self, token, command):
    335         raise self.error(token, "Invalid block tag: '%s'" % command)
    336 
    337     def unclosed_block_tag(self, parse_until):
    338         raise self.error(None, "Unclosed tags: %s " %  ', '.join(parse_until))
    339 
    340     def compile_function_error(self, token, e):
    341         pass
    342 
    343     def next_token(self):
    344         return self.tokens.pop(0)
    345 
    346     def prepend_token(self, token):
    347         self.tokens.insert(0, token)
    348 
    349     def delete_first_token(self):
    350         del self.tokens[0]
    351 
    352     def add_library(self, lib):
    353         self.tags.update(lib.tags)
    354         self.filters.update(lib.filters)
    355 
    356     def compile_filter(self, token):
    357         "Convenient wrapper for FilterExpression"
    358         return FilterExpression(token, self)
    359 
    360     def find_filter(self, filter_name):
    361         if filter_name in self.filters:
    362             return self.filters[filter_name]
    363         else:
    364             raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
    365 
    366 class TokenParser(object):
    367     """
    368     Subclass this and implement the top() method to parse a template line. When
    369     instantiating the parser, pass in the line from the Django template parser.
    370 
    371     The parser's "tagname" instance-variable stores the name of the tag that
    372     the filter was called with.
    373     """
    374     def __init__(self, subject):
    375         self.subject = subject
    376         self.pointer = 0
    377         self.backout = []
    378         self.tagname = self.tag()
    379 
    380     def top(self):
    381         "Overload this method to do the actual parsing and return the result."
    382         raise NotImplementedError()
    383 
    384     def more(self):
    385         "Returns True if there is more stuff in the tag."
    386         return self.pointer < len(self.subject)
    387 
    388     def back(self):
    389         "Undoes the last microparser. Use this for lookahead and backtracking."
    390         if not len(self.backout):
    391             raise TemplateSyntaxError("back called without some previous parsing")
    392         self.pointer = self.backout.pop()
    393 
    394     def tag(self):
    395         "A microparser that just returns the next tag from the line."
    396         subject = self.subject
    397         i = self.pointer
    398         if i >= len(subject):
    399             raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
    400         p = i
    401         while i < len(subject) and subject[i] not in (' ', '\t'):
    402             i += 1
    403         s = subject[p:i]
    404         while i < len(subject) and subject[i] in (' ', '\t'):
    405             i += 1
    406         self.backout.append(self.pointer)
    407         self.pointer = i
    408         return s
    409 
    410     def value(self):
    411         "A microparser that parses for a value: some string constant or variable name."
    412         subject = self.subject
    413         i = self.pointer
    414         if i >= len(subject):
    415             raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
    416         if subject[i] in ('"', "'"):
    417             p = i
    418             i += 1
    419             while i < len(subject) and subject[i] != subject[p]:
    420                 i += 1
    421             if i >= len(subject):
    422                 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
    423             i += 1
    424             res = subject[p:i]
    425             while i < len(subject) and subject[i] in (' ', '\t'):
    426                 i += 1
    427             self.backout.append(self.pointer)
    428             self.pointer = i
    429             return res
    430         else:
    431             p = i
    432             while i < len(subject) and subject[i] not in (' ', '\t'):
    433                 if subject[i] in ('"', "'"):
    434                     c = subject[i]
    435                     i += 1
    436                     while i < len(subject) and subject[i] != c:
    437                         i += 1
    438                     if i >= len(subject):
    439                         raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
    440                 i += 1
    441             s = subject[p:i]
    442             while i < len(subject) and subject[i] in (' ', '\t'):
    443                 i += 1
    444             self.backout.append(self.pointer)
    445             self.pointer = i
    446             return s
    447 
    448 # This only matches constant *strings* (things in quotes or marked for
    449 # translation). Numbers are treated as variables for implementation reasons
    450 # (so that they retain their type when passed to filters).
    451 constant_string = r"""
    452 (?:%(i18n_open)s%(strdq)s%(i18n_close)s|
    453 %(i18n_open)s%(strsq)s%(i18n_close)s|
    454 %(strdq)s|
    455 %(strsq)s)
    456 """ % {
    457     'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string
    458     'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string
    459     'i18n_open' : re.escape("_("),
    460     'i18n_close' : re.escape(")"),
    461     }
    462 constant_string = constant_string.replace("\n", "")
    463 
    464 filter_raw_string = r"""
    465 ^(?P<constant>%(constant)s)|
    466 ^(?P<var>[%(var_chars)s]+|%(num)s)|
    467  (?:%(filter_sep)s
    468      (?P<filter_name>\w+)
    469          (?:%(arg_sep)s
    470              (?:
    471               (?P<constant_arg>%(constant)s)|
    472               (?P<var_arg>[%(var_chars)s]+|%(num)s)
    473              )
    474          )?
    475  )""" % {
    476     'constant': constant_string,
    477     'num': r'[-+\.]?\d[\d\.e]*',
    478     'var_chars': "\w\." ,
    479     'filter_sep': re.escape(FILTER_SEPARATOR),
    480     'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
    481   }
    482 
    483 filter_re = re.compile(filter_raw_string, re.UNICODE|re.VERBOSE)
    484 
    485 class FilterExpression(object):
    486     r"""
    487     Parses a variable token and its optional filters (all as a single string),
    488     and return a list of tuples of the filter name and arguments.
    489     Sample:
    490         >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
    491         >>> p = Parser('')
    492         >>> fe = FilterExpression(token, p)
    493         >>> len(fe.filters)
    494         2
    495         >>> fe.var
    496         <Variable: 'variable'>
    497 
    498     This class should never be instantiated outside of the
    499     get_filters_from_token helper function.
    500     """
    501     def __init__(self, token, parser):
    502         self.token = token
    503         matches = filter_re.finditer(token)
    504         var_obj = None
    505         filters = []
    506         upto = 0
    507         for match in matches:
    508             start = match.start()
    509             if upto != start:
    510                 raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s"  % \
    511                         (token[:upto], token[upto:start], token[start:]))
    512             if var_obj is None:
    513                 var, constant = match.group("var", "constant")
    514                 if constant:
    515                     try:
    516                         var_obj = Variable(constant).resolve({})
    517                     except VariableDoesNotExist:
    518                         var_obj = None
    519                 elif var is None:
    520                     raise TemplateSyntaxError("Could not find variable at start of %s." % token)
    521                 elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
    522                     raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)
    523                 else:
    524                     var_obj = Variable(var)
    525             else:
    526                 filter_name = match.group("filter_name")
    527                 args = []
    528                 constant_arg, var_arg = match.group("constant_arg", "var_arg")
    529                 if constant_arg:
    530                     args.append((False, Variable(constant_arg).resolve({})))
    531                 elif var_arg:
    532                     args.append((True, Variable(var_arg)))
    533                 filter_func = parser.find_filter(filter_name)
    534                 self.args_check(filter_name,filter_func, args)
    535                 filters.append( (filter_func,args))
    536             upto = match.end()
    537         if upto != len(token):
    538             raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))
    539 
    540         self.filters = filters
    541         self.var = var_obj
    542 
    543     def resolve(self, context, ignore_failures=False):
    544         if isinstance(self.var, Variable):
    545             try:
    546                 obj = self.var.resolve(context)
    547             except VariableDoesNotExist:
    548                 if ignore_failures:
    549                     obj = None
    550                 else:
    551                     if settings.TEMPLATE_STRING_IF_INVALID:
    552                         global invalid_var_format_string
    553                         if invalid_var_format_string is None:
    554                             invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID
    555                         if invalid_var_format_string:
    556                             return settings.TEMPLATE_STRING_IF_INVALID % self.var
    557                         return settings.TEMPLATE_STRING_IF_INVALID
    558                     else:
    559                         obj = settings.TEMPLATE_STRING_IF_INVALID
    560         else:
    561             obj = self.var
    562         for func, args in self.filters:
    563             arg_vals = []
    564             for lookup, arg in args:
    565                 if not lookup:
    566                     arg_vals.append(mark_safe(arg))
    567                 else:
    568                     arg_vals.append(arg.resolve(context))
    569             if getattr(func, 'needs_autoescape', False):
    570                 new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
    571             else:
    572                 new_obj = func(obj, *arg_vals)
    573             if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
    574                 obj = mark_safe(new_obj)
    575             elif isinstance(obj, EscapeData):
    576                 obj = mark_for_escaping(new_obj)
    577             else:
    578                 obj = new_obj
    579         return obj
    580 
    581     def args_check(name, func, provided):
    582         provided = list(provided)
    583         plen = len(provided)
    584         # Check to see if a decorator is providing the real function.
    585         func = getattr(func, '_decorated_function', func)
    586         args, varargs, varkw, defaults = getargspec(func)
    587         # First argument is filter input.
    588         args.pop(0)
    589         if defaults:
    590             nondefs = args[:-len(defaults)]
    591         else:
    592             nondefs = args
    593         # Args without defaults must be provided.
    594         try:
    595             for arg in nondefs:
    596                 provided.pop(0)
    597         except IndexError:
    598             # Not enough
    599             raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
    600 
    601         # Defaults can be overridden.
    602         defaults = defaults and list(defaults) or []
    603         try:
    604             for parg in provided:
    605                 defaults.pop(0)
    606         except IndexError:
    607             # Too many.
    608             raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))
    609 
    610         return True
    611     args_check = staticmethod(args_check)
    612 
    613     def __str__(self):
    614         return self.token
    615 
    616 def resolve_variable(path, context):
    617     """
    618     Returns the resolved variable, which may contain attribute syntax, within
    619     the given context.
    620 
    621     Deprecated; use the Variable class instead.
    622     """
    623     return Variable(path).resolve(context)
    624 
    625 class Variable(object):
    626     r"""
    627     A template variable, resolvable against a given context. The variable may be
    628     a hard-coded string (if it begins and ends with single or double quote
    629     marks)::
    630 
    631         >>> c = {'article': {'section':u'News'}}
    632         >>> Variable('article.section').resolve(c)
    633         u'News'
    634         >>> Variable('article').resolve(c)
    635         {'section': u'News'}
    636         >>> class AClass: pass
    637         >>> c = AClass()
    638         >>> c.article = AClass()
    639         >>> c.article.section = u'News'
    640 
    641     (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
    642     """
    643 
    644     def __init__(self, var):
    645         self.var = var
    646         self.literal = None
    647         self.lookups = None
    648         self.translate = False
    649 
    650         try:
    651             # First try to treat this variable as a number.
    652             #
    653             # Note that this could cause an OverflowError here that we're not
    654             # catching. Since this should only happen at compile time, that's
    655             # probably OK.
    656             self.literal = float(var)
    657 
    658             # So it's a float... is it an int? If the original value contained a
    659             # dot or an "e" then it was a float, not an int.
    660             if '.' not in var and 'e' not in var.lower():
    661                 self.literal = int(self.literal)
    662 
    663             # "2." is invalid
    664             if var.endswith('.'):
    665                 raise ValueError
    666 
    667         except ValueError:
    668             # A ValueError means that the variable isn't a number.
    669             if var.startswith('_(') and var.endswith(')'):
    670                 # The result of the lookup should be translated at rendering
    671                 # time.
    672                 self.translate = True
    673                 var = var[2:-1]
    674             # If it's wrapped with quotes (single or double), then
    675             # we're also dealing with a literal.
    676             try:
    677                 self.literal = mark_safe(unescape_string_literal(var))
    678             except ValueError:
    679                 # Otherwise we'll set self.lookups so that resolve() knows we're
    680                 # dealing with a bonafide variable
    681                 self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))
    682 
    683     def resolve(self, context):
    684         """Resolve this variable against a given context."""
    685         if self.lookups is not None:
    686             # We're dealing with a variable that needs to be resolved
    687             value = self._resolve_lookup(context)
    688         else:
    689             # We're dealing with a literal, so it's already been "resolved"
    690             value = self.literal
    691         if self.translate:
    692             return _(value)
    693         return value
    694 
    695     def __repr__(self):
    696         return "<%s: %r>" % (self.__class__.__name__, self.var)
    697 
    698     def __str__(self):
    699         return self.var
    700 
    701     def _resolve_lookup(self, context):
    702         """
    703         Performs resolution of a real variable (i.e. not a literal) against the
    704         given context.
    705 
    706         As indicated by the method's name, this method is an implementation
    707         detail and shouldn't be called by external code. Use Variable.resolve()
    708         instead.
    709         """
    710         current = context
    711         for bit in self.lookups:
    712             try: # dictionary lookup
    713                 current = current[bit]
    714             except (TypeError, AttributeError, KeyError):
    715                 try: # attribute lookup
    716                     current = getattr(current, bit)
    717                     if callable(current):
    718                         if getattr(current, 'alters_data', False):
    719                             current = settings.TEMPLATE_STRING_IF_INVALID
    720                         else:
    721                             try: # method call (assuming no args required)
    722                                 current = current()
    723                             except TypeError: # arguments *were* required
    724                                 # GOTCHA: This will also catch any TypeError
    725                                 # raised in the function itself.
    726                                 current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
    727                             except Exception, e:
    728                                 if getattr(e, 'silent_variable_failure', False):
    729                                     current = settings.TEMPLATE_STRING_IF_INVALID
    730                                 else:
    731                                     raise
    732                 except (TypeError, AttributeError):
    733                     try: # list-index lookup
    734                         current = current[int(bit)]
    735                     except (IndexError, # list index out of range
    736                             ValueError, # invalid literal for int()
    737                             KeyError,   # current is a dict without `int(bit)` key
    738                             TypeError,  # unsubscriptable object
    739                             ):
    740                         raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
    741                 except Exception, e:
    742                     if getattr(e, 'silent_variable_failure', False):
    743                         current = settings.TEMPLATE_STRING_IF_INVALID
    744                     else:
    745                         raise
    746 
    747         return current
    748 
    749 class Node(object):
    750     # Set this to True for nodes that must be first in the template (although
    751     # they can be preceded by text nodes.
    752     must_be_first = False
    753 
    754     def render(self, context):
    755         "Return the node rendered as a string"
    756         pass
    757 
    758     def __iter__(self):
    759         yield self
    760 
    761     def get_nodes_by_type(self, nodetype):
    762         "Return a list of all nodes (within this node and its nodelist) of the given type"
    763         nodes = []
    764         if isinstance(self, nodetype):
    765             nodes.append(self)
    766         if hasattr(self, 'nodelist'):
    767             nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
    768         return nodes
    769 
    770 class NodeList(list):
    771     # Set to True the first time a non-TextNode is inserted by
    772     # extend_nodelist().
    773     contains_nontext = False
    774 
    775     def render(self, context):
    776         bits = []
    777         for node in self:
    778             if isinstance(node, Node):
    779                 bits.append(self.render_node(node, context))
    780             else:
    781                 bits.append(node)
    782         return mark_safe(''.join([force_unicode(b) for b in bits]))
    783 
    784     def get_nodes_by_type(self, nodetype):
    785         "Return a list of all nodes of the given type"
    786         nodes = []
    787         for node in self:
    788             nodes.extend(node.get_nodes_by_type(nodetype))
    789         return nodes
    790 
    791     def render_node(self, node, context):
    792         return node.render(context)
    793 
    794 class TextNode(Node):
    795     def __init__(self, s):
    796         self.s = s
    797 
    798     def __repr__(self):
    799         return "<Text Node: '%s'>" % smart_str(self.s[:25], 'ascii',
    800                 errors='replace')
    801 
    802     def render(self, context):
    803         return self.s
    804    
    805 def _render_value_in_context(value, context):
    806     """
    807     Converts any value to a string to become part of a rendered template. This
    808     means escaping, if required, and conversion to a unicode object. If value
    809     is a string, it is expected to have already been translated.
    810     """
    811     value = force_unicode(value)
    812     if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData):
    813         return escape(value)
    814     else:
    815         return value
    816 
    817 class VariableNode(Node):
    818     def __init__(self, filter_expression):
    819         self.filter_expression = filter_expression
    820 
    821     def __repr__(self):
    822         return "<Variable Node: %s>" % self.filter_expression
    823 
    824     def render(self, context):
    825         try:
    826             output = self.filter_expression.resolve(context)
    827         except UnicodeDecodeError:
    828             # Unicode conversion can fail sometimes for reasons out of our
    829             # control (e.g. exception rendering). In that case, we fail quietly.
    830             return ''
    831         return _render_value_in_context(output, context)
    832 
    833 def generic_tag_compiler(params, defaults, name, node_class, parser, token):
    834     "Returns a template.Node subclass."
    835     bits = token.split_contents()[1:]
    836     bmax = len(params)
    837     def_len = defaults and len(defaults) or 0
    838     bmin = bmax - def_len
    839     if(len(bits) < bmin or len(bits) > bmax):
    840         if bmin == bmax:
    841             message = "%s takes %s arguments" % (name, bmin)
    842         else:
    843             message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
    844         raise TemplateSyntaxError(message)
    845     return node_class(bits)
    846 
    847 class Library(object):
    848     def __init__(self):
    849         self.filters = {}
    850         self.tags = {}
    851 
    852     def tag(self, name=None, compile_function=None):
    853         if name == None and compile_function == None:
    854             # @register.tag()
    855             return self.tag_function
    856         elif name != None and compile_function == None:
    857             if(callable(name)):
    858                 # @register.tag
    859                 return self.tag_function(name)
    860             else:
    861                 # @register.tag('somename') or @register.tag(name='somename')
    862                 def dec(func):
    863                     return self.tag(name, func)
    864                 return dec
    865         elif name != None and compile_function != None:
    866             # register.tag('somename', somefunc)
    867             self.tags[name] = compile_function
    868             return compile_function
    869         else:
    870             raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
    871 
    872     def tag_function(self,func):
    873         self.tags[getattr(func, "_decorated_function", func).__name__] = func
    874         return func
    875 
    876     def filter(self, name=None, filter_func=None):
    877         if name == None and filter_func == None:
    878             # @register.filter()
    879             return self.filter_function
    880         elif filter_func == None:
    881             if(callable(name)):
    882                 # @register.filter
    883                 return self.filter_function(name)
    884             else:
    885                 # @register.filter('somename') or @register.filter(name='somename')
    886                 def dec(func):
    887                     return self.filter(name, func)
    888                 return dec
    889         elif name != None and filter_func != None:
    890             # register.filter('somename', somefunc)
    891             self.filters[name] = filter_func
    892             return filter_func
    893         else:
    894             raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
    895 
    896     def filter_function(self, func):
    897         self.filters[getattr(func, "_decorated_function", func).__name__] = func
    898         return func
    899 
    900     def simple_tag(self,func):
    901         params, xx, xxx, defaults = getargspec(func)
    902 
    903         class SimpleNode(Node):
    904             def __init__(self, vars_to_resolve):
    905                 self.vars_to_resolve = map(Variable, vars_to_resolve)
    906 
    907             def render(self, context):
    908                 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
    909                 return func(*resolved_vars)
    910 
    911         compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
    912         compile_func.__doc__ = func.__doc__
    913         self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
    914         return func
    915 
    916     def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
    917         def dec(func):
    918             params, xx, xxx, defaults = getargspec(func)
    919             if takes_context:
    920                 if params[0] == 'context':
    921                     params = params[1:]
    922                 else:
    923                     raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
    924 
    925             class InclusionNode(Node):
    926                 def __init__(self, vars_to_resolve):
    927                     self.vars_to_resolve = map(Variable, vars_to_resolve)
    928 
    929                 def render(self, context):
    930                     resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]
    931                     if takes_context:
    932                         args = [context] + resolved_vars
    933                     else:
    934                         args = resolved_vars
    935 
    936                     dict = func(*args)
    937 
    938                     if not getattr(self, 'nodelist', False):
    939                         from django.template.loader import get_template, select_template
    940                         if not isinstance(file_name, basestring) and is_iterable(file_name):
    941                             t = select_template(file_name)
    942                         else:
    943                             t = get_template(file_name)
    944                         self.nodelist = t.nodelist
    945                     return self.nodelist.render(context_class(dict,
    946                             autoescape=context.autoescape))
    947 
    948             compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
    949             compile_func.__doc__ = func.__doc__
    950             self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
    951             return func
    952         return dec
    953 
    954 def get_library(module_name):
    955     lib = libraries.get(module_name, None)
    956     if not lib:
    957         try:
    958             mod = import_module(module_name)
    959         except ImportError, e:
    960             raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
    961         try:
    962             lib = mod.register
    963             libraries[module_name] = lib
    964         except AttributeError:
    965             raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
    966     return lib
    967 
    968 def add_to_builtins(module_name):
    969     builtins.append(get_library(module_name))
    970 
    971 add_to_builtins('django.template.defaulttags')
    972 add_to_builtins('django.template.defaultfilters')
  • django/template/utils.py

     
     1import re
     2from django.utils.encoding import smart_str
     3from django.template.nodes import Node, NodeList
     4from django.template.parser import TokenSyntaxError, TemplateSyntaxError
     5
     6class EmptyNode(Node):
     7    def render(self, context):
     8        return u''
     9       
     10class ConditionalNode(Node):
     11    def __init__(self, nodelist_true, nodelist_false):
     12        self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
     13
     14    def __iter__(self):
     15        for node in self.nodelist_true:
     16            yield node
     17        for node in self.nodelist_false:
     18            yield node
     19
     20    def get_nodes_by_type(self, nodetype):
     21        nodes = []
     22        if isinstance(self, nodetype):
     23            nodes.append(self)
     24        nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
     25        nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
     26        return nodes
     27       
     28    def check_condition(self, context):
     29        return False
     30       
     31    def render(self, context):
     32        if self.check_condition(context):
     33            return self.nodelist_true.render(context)
     34        elif self.nodelist_false:
     35            return self.nodelist_false.render(context)
     36        return u""
     37       
     38def parse_conditional_nodelists(parser, name):
     39    end_tag = 'end' + name
     40    nodelist_true = parser.parse(('else', end_tag))
     41    token = parser.next_token()
     42    if token.contents == 'else':
     43        nodelist_false = parser.parse((end_tag,))
     44        parser.delete_first_token()
     45    else:
     46        nodelist_false = NodeList()
     47    return nodelist_true, nodelist_false
     48
     49def parse_args_and_kwargs(bits, until=()):
     50    args = []
     51    kwargs = {}
     52    while True:
     53        name = bits.pop_name()
     54        if name in until:
     55            bits.pushback()
     56            break
     57        if name and bits.pop_lexeme('='):
     58            kwargs[name] = bits.parse_expression(required=True)
     59        else:
     60            if name:
     61                bits.pushback()
     62            try:
     63                args.append(bits.parse_expression())
     64            except TokenSyntaxError:
     65                break
     66        if not bits.pop_lexeme(','):
     67            break
     68    return args, kwargs
     69
     70def parse_as(bits, required=False):
     71    if bits.pop_lexeme('as', required=required):
     72        return bits.pop_name(required=required)
     73    return None
     74
     75def parse_context_bindings(bits, empty=False):
     76    bindings = {}
     77    try:
     78        while True:
     79            expression = bits.parse_expression()
     80            name = parse_as(bits, required=True)
     81            bindings[name] = expression
     82            if not bits.pop_lexeme('and'):
     83                break
     84    except TokenSyntaxError:
     85        if bindings or not bindings and not empty:
     86            bits.expected('<expression> as <name>')
     87    return bindings
     88
     89def resolve_list(expressions, context, safe=False, default=None, ignore_errors=False):
     90    result = []
     91    for exp in expressions:
     92        try:
     93            value = exp.resolve(context)
     94        except LookupError:
     95            if not safe and not ignore_errors:
     96                raise
     97            if safe:
     98                value = default
     99        else:
     100            result.append(value)
     101    return result
     102
     103def resolve_dict(expressions, context, kwargs=False, safe=False, default=None, ignore_errors=False):
     104    result = {}
     105    for key, exp in expressions.items():
     106        try:
     107            value = exp.resolve(context)
     108        except LookupError:
     109            if not safe and not ignore_errors:
     110                raise
     111            if safe:
     112                value = default
     113        else:
     114            result[kwargs and smart_str(key, 'ascii') or key] = value
     115    return result
  • django/template/expressions.py

     
     1from django.conf import settings
     2from django.utils.encoding import force_unicode
     3from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
     4
     5__all__ = ('LookupError', 'Expression', 'Variable', 'Literal', 'Lookup', 'FilterExpression')
     6
     7class LookupError(Exception):
     8    def __init__(self, var, msg, params=()):
     9        self.var = var
     10        self.msg = msg
     11        self.params = params
     12
     13    def __str__(self):
     14        return unicode(self).encode('utf-8')
     15
     16    def __unicode__(self):
     17        return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])
     18
     19
     20class Expression(object):
     21    def __init__(self, token=None):
     22        self.token = token
     23
     24    def resolve_safe(self, context, default=None):
     25        try:
     26            return self.resolve(context)
     27        except LookupError:
     28            return default
     29
     30    def resolve(self, context, ignore_failures=False):
     31        return None
     32
     33
     34class Literal(Expression):
     35    def __init__(self, value, token=None):
     36        super(Literal, self).__init__(token=token)
     37        self.value = value
     38
     39    def resolve(self, context, ignore_failures=False):
     40        return self.value
     41       
     42    def __repr__(self):
     43        return '<Literal %s>' % repr(self.value)
     44       
     45       
     46def handle_lookup_exception(e):
     47    if getattr(e, 'silent_variable_failure', False):
     48        return settings.TEMPLATE_STRING_IF_INVALID
     49    raise e
     50       
     51class Lookup(Expression):
     52    def __init__(self, lookups, var=None, token=None):
     53        super(Lookup, self).__init__(token=token)   
     54        self.var = var
     55        self.lookups = lookups
     56
     57    def __str__(self):
     58        return "%s" % self.var
     59       
     60    def __repr__(self):
     61        return "<Lookup %s>" % self.var
     62       
     63    def resolve(self, context, ignore_failures=False):
     64        current = context
     65        try:
     66            for bit in self.lookups:
     67                try: # dictionary lookup
     68                    current = current[bit]
     69                except (TypeError, AttributeError, KeyError):
     70                    try: # attribute lookup
     71                        current = getattr(current, bit)
     72                        if callable(current):
     73                            if getattr(current, 'alters_data', False):
     74                                current = settings.TEMPLATE_STRING_IF_INVALID
     75                            else:
     76                                try: # method call (assuming no args required)
     77                                    current = current()
     78                                except TypeError: # arguments *were* required
     79                                    # GOTCHA: This will also catch any TypeError
     80                                    # raised in the function itself.
     81                                    current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
     82                                except Exception, e:
     83                                    current = handle_lookup_exception(e)
     84                    except (TypeError, AttributeError):
     85                        try: # list-index lookup
     86                            current = current[int(bit)]
     87                        except (IndexError, # list index out of range
     88                                ValueError, # invalid literal for int()
     89                                KeyError,   # current is a dict without `int(bit)` key
     90                                TypeError,  # unsubscriptable object
     91                                ):
     92                            raise LookupError(self.var, "Failed lookup for key [%s] in %r", (bit, current))
     93                    except Exception, e:
     94                        current = handle_lookup_exception(e)
     95        except LookupError:
     96            if ignore_failures:
     97                return None
     98            raise
     99        return current
     100
     101class FilterExpression(Expression):
     102    def __init__(self, root, filters, token=None):
     103        super(FilterExpression, self).__init__(token=token)   
     104        self.root = root
     105        self.filters = filters
     106       
     107    def __repr__(self):
     108        return "<FilterExpression %s>" % "|".join(["%s:%s" % (func.__name__, arg) for func, arg in self.filters])
     109
     110    def resolve(self, context, ignore_failures=False):
     111        try:
     112            obj = self.root.resolve(context)
     113        except LookupError:
     114            if ignore_failures:
     115                obj = None
     116            elif not self.filters:
     117                raise
     118            else:
     119                obj = settings.TEMPLATE_STRING_IF_INVALID
     120        for func, args in self.filters:
     121            arg_vals = []         
     122            for arg in args:
     123                arg_vals.append(arg.resolve(context))
     124            if getattr(func, 'needs_autoescape', False):
     125                new_obj = func(obj, autoescape=context.autoescape, *arg_vals)
     126            else:
     127                new_obj = func(obj, *arg_vals)
     128            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
     129                obj = mark_safe(new_obj)
     130            elif isinstance(obj, EscapeData):
     131                obj = mark_for_escaping(new_obj)
     132            else:
     133                obj = new_obj
     134        return obj
     135
     136    def __str__(self):
     137        return str(self.root)+'|<filtered>'
  • django/template/defaulttags.py

     
    88except NameError:
    99    from django.utils.itercompat import reversed     # Python 2.3 fallback
    1010
    11 from django.template import Node, NodeList, Template, Context, Variable
    12 from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
    13 from django.template import get_library, Library, InvalidTemplateLibrary
     11from django.template import Node, NodeList, Template, Context
     12from django.template.library import get_library, Library, InvalidTemplateLibrary
     13from django.template.parser import uses_token_stream, TokenSyntaxError, TemplateSyntaxError, parse_lookup
     14from django.template.parser import BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
     15from django.template.utils import EmptyNode, ConditionalNode, parse_conditional_nodelists, parse_as, parse_args_and_kwargs, resolve_list, resolve_dict
    1416from django.conf import settings
    15 from django.utils.encoding import smart_str, smart_unicode
    1617from django.utils.itercompat import groupby
    1718from django.utils.safestring import mark_safe
    1819
     
    3334        else:
    3435            return output
    3536
    36 class CommentNode(Node):
    37     def render(self, context):
    38         return ''
     37class CommentNode(EmptyNode): pass
    3938
    4039class CycleNode(Node):
    4140    def __init__(self, cyclevars, variable_name=None):
     
    7473
    7574    def render(self, context):
    7675        for var in self.vars:
    77             value = var.resolve(context, True)
     76            value = var.resolve_safe(context)
    7877            if value:
    79                 return smart_unicode(value)
     78                return value
    8079        return u''
    8180
    8281class ForNode(Node):
     
    114113            parentloop = context['forloop']
    115114        else:
    116115            parentloop = {}
    117         context.push()
    118         try:
    119             values = self.sequence.resolve(context, True)
    120         except VariableDoesNotExist:
    121             values = []
     116        context.push() 
     117        values = self.sequence.resolve_safe(context, [])
    122118        if values is None:
    123119            values = []
    124120        if not hasattr(values, '__len__'):
     
    163159        context.pop()
    164160        return nodelist.render(context)
    165161
    166 class IfChangedNode(Node):
    167     def __init__(self, nodelist_true, nodelist_false, *varlist):
    168         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
     162class IfChangedNode(ConditionalNode):
     163    def __init__(self, nodelist_true, nodelist_false, expressions):
     164        super(IfChangedNode, self).__init__(nodelist_true, nodelist_false)
     165        self.expressions = expressions       
    169166        self._last_seen = None
    170         self._varlist = varlist
    171167        self._id = str(id(self))
    172168
    173     def render(self, context):
     169    def check_condition(self, context):       
    174170        if 'forloop' in context and self._id not in context['forloop']:
    175171            self._last_seen = None
    176172            context['forloop'][self._id] = 1
    177         try:
    178             if self._varlist:
    179                 # Consider multiple parameters.  This automatically behaves
    180                 # like an OR evaluation of the multiple variables.
    181                 compare_to = [var.resolve(context, True) for var in self._varlist]
    182             else:
    183                 compare_to = self.nodelist_true.render(context)
    184         except VariableDoesNotExist:
    185             compare_to = None
     173        if self.expressions:
     174            # Consider multiple parameters.  This automatically behaves
     175            # like an OR evaluation of the multiple variables.
     176            try:
     177                compare_to = [var.resolve(context, True) for var in self.expressions]
     178            except LookupError:
     179                compare_to = None
     180        else:
     181            compare_to = self.nodelist_true.render(context)
    186182
    187183        if compare_to != self._last_seen:
    188             firstloop = (self._last_seen == None)
    189184            self._last_seen = compare_to
    190             content = self.nodelist_true.render(context)
    191             return content
    192         elif self.nodelist_false:
    193             return self.nodelist_false.render(context)
    194         return ''
     185            return True       
     186        return False
    195187
    196 class IfEqualNode(Node):
     188class IfEqualNode(ConditionalNode):
    197189    def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
     190        super(IfEqualNode, self).__init__(nodelist_true, nodelist_false)
    198191        self.var1, self.var2 = var1, var2
    199         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    200192        self.negate = negate
    201193
    202     def __repr__(self):
    203         return "<IfEqualNode>"
     194    def check_condition(self, context):
     195        val1 = self.var1.resolve_safe(context)
     196        val2 = self.var2.resolve_safe(context)
     197        return (self.negate and val1 != val2) or (not self.negate and val1 == val2)
    204198
    205     def render(self, context):
    206         val1 = self.var1.resolve(context, True)
    207         val2 = self.var2.resolve(context, True)
    208         if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
    209             return self.nodelist_true.render(context)
    210         return self.nodelist_false.render(context)
    211 
    212 class IfNode(Node):
     199class IfNode(ConditionalNode):
    213200    def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
     201        super(IfNode, self).__init__(nodelist_true, nodelist_false)
    214202        self.bool_exprs = bool_exprs
    215         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    216203        self.link_type = link_type
    217204
    218     def __repr__(self):
    219         return "<If node>"
     205    def check_condition(self, context):
     206        or_link = self.link_type == IfNode.LinkTypes.or_
     207        for ifnot, bool_expr in self.bool_exprs:
     208            value = bool(bool_expr.resolve_safe(context, False))
     209            if (ifnot != value) == or_link:
     210                return or_link
     211        return not or_link
    220212
    221     def __iter__(self):
    222         for node in self.nodelist_true:
    223             yield node
    224         for node in self.nodelist_false:
    225             yield node
    226 
    227     def get_nodes_by_type(self, nodetype):
    228         nodes = []
    229         if isinstance(self, nodetype):
    230             nodes.append(self)
    231         nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
    232         nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
    233         return nodes
    234 
    235     def render(self, context):
    236         if self.link_type == IfNode.LinkTypes.or_:
    237             for ifnot, bool_expr in self.bool_exprs:
    238                 try:
    239                     value = bool_expr.resolve(context, True)
    240                 except VariableDoesNotExist:
    241                     value = None
    242                 if (value and not ifnot) or (ifnot and not value):
    243                     return self.nodelist_true.render(context)
    244             return self.nodelist_false.render(context)
    245         else:
    246             for ifnot, bool_expr in self.bool_exprs:
    247                 try:
    248                     value = bool_expr.resolve(context, True)
    249                 except VariableDoesNotExist:
    250                     value = None
    251                 if not ((value and not ifnot) or (ifnot and not value)):
    252                     return self.nodelist_false.render(context)
    253             return self.nodelist_true.render(context)
    254 
    255213    class LinkTypes:
    256214        and_ = 0,
    257215        or_ = 1
     
    309267                    return '' # Fail silently for invalid included templates.
    310268        return output
    311269
    312 class LoadNode(Node):
    313     def render(self, context):
    314         return ''
     270class LoadNode(EmptyNode): pass
    315271
    316272class NowNode(Node):
    317273    def __init__(self, format_string):
     
    321277        from datetime import datetime
    322278        from django.utils.dateformat import DateFormat
    323279        df = DateFormat(datetime.now())
    324         return df.format(self.format_string)
     280        return df.format(self.format_string.resolve_safe(context, ""))
    325281
    326282class SpacelessNode(Node):
    327283    def __init__(self, nodelist):
     
    357313
    358314    def render(self, context):
    359315        from django.core.urlresolvers import reverse, NoReverseMatch
    360         args = [arg.resolve(context) for arg in self.args]
    361         kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
    362                        for k, v in self.kwargs.items()])
     316        args = resolve_list(self.args, context)
     317        kwargs = resolve_dict(self.kwargs, context, kwargs=True)
    363318
    364319        # Try to look up the URL twice: once given the view name, and again
    365320        # relative to what we guess is the "main" app. If they both fail,
     
    401356            value = self.val_expr.resolve(context)
    402357            maxvalue = self.max_expr.resolve(context)
    403358            max_width = int(self.max_width.resolve(context))
    404         except VariableDoesNotExist:
    405             return ''
     359        except LookupError:
     360            return u""
    406361        except ValueError:
    407362            raise TemplateSyntaxError("widthratio final argument must be an number")
    408363        try:
     
    441396    arg = args[1]
    442397    if arg not in (u'on', u'off'):
    443398        raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
    444     nodelist = parser.parse(('endautoescape',))
    445     parser.delete_first_token()
     399    nodelist = parser.parse_nodelist('endautoescape')
    446400    return AutoEscapeControlNode((arg == 'on'), nodelist)
    447401autoescape = register.tag(autoescape)
    448402
     
    556510    for func, unused in filter_expr.filters:
    557511        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
    558512            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
    559     nodelist = parser.parse(('endfilter',))
    560     parser.delete_first_token()
     513    nodelist = parser.parse_nodelist('endfilter')
    561514    return FilterNode(filter_expr, nodelist)
    562515do_filter = register.tag("filter", do_filter)
    563516
    564517#@register.tag
    565 def firstof(parser, token):
     518#@uses_token_stream
     519def firstof(parser, bits):
    566520    """
    567521    Outputs the first variable passed that is not False, without escaping.
    568522
     
    596550        {% endfilter %}
    597551
    598552    """
    599     bits = token.split_contents()[1:]
    600     if len(bits) < 1:
    601         raise TemplateSyntaxError("'firstof' statement requires at least one"
    602                                   " argument")
    603     return FirstOfNode([parser.compile_filter(bit) for bit in bits])
    604 firstof = register.tag(firstof)
     553    return FirstOfNode(bits.parse_expression_list(minimum=1))
     554firstof = register.tag(uses_token_stream(firstof))
    605555
    606556#@register.tag(name="for")
    607 def do_for(parser, token):
     557#@uses_token_stream
     558def do_for(parser, bits):
    608559    """
    609560    Loops over each item in an array.
    610561
     
    667618        ==========================  ================================================
    668619
    669620    """
    670     bits = token.contents.split()
    671     if len(bits) < 4:
    672         raise TemplateSyntaxError("'for' statements should have at least four"
    673                                   " words: %s" % token.contents)
     621    loopvars = []
     622    while True:
     623        name = bits.pop_name()
     624        if not name:
     625            if loopvars:
     626                bits.pushback() #pushback the trailing comma
     627            break
     628        if name == 'in':
     629            if loopvars:
     630                bits.pushback() #pushback the trailing comma       
     631            bits.pushback()
     632            break
     633        loopvars.append(name)
     634        if not bits.pop_lexeme(','):
     635            break
     636    if not loopvars:
     637        raise TemplateSyntaxError("'for' statements should use the format: for loopvar[, ...] in expression")
    674638
    675     is_reversed = bits[-1] == 'reversed'
    676     in_index = is_reversed and -3 or -2
    677     if bits[in_index] != 'in':
    678         raise TemplateSyntaxError("'for' statements should use the format"
    679                                   " 'for x in y': %s" % token.contents)
    680 
    681     loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
    682     for var in loopvars:
    683         if not var or ' ' in var:
    684             raise TemplateSyntaxError("'for' tag received an invalid argument:"
    685                                       " %s" % token.contents)
    686 
    687     sequence = parser.compile_filter(bits[in_index+1])
     639    bits.pop_lexeme('in', required=True)
     640    sequence = bits.parse_expression(required=True)   
     641    is_reversed = bits.pop_lexeme('reversed')
     642   
    688643    nodelist_loop = parser.parse(('empty', 'endfor',))
    689644    token = parser.next_token()
    690645    if token.contents == 'empty':
    691         nodelist_empty = parser.parse(('endfor',))
    692         parser.delete_first_token()
     646        nodelist_empty = parser.parse_nodelist('endfor')
    693647    else:
    694648        nodelist_empty = None
    695649    return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty)
    696 do_for = register.tag("for", do_for)
     650do_for = register.tag("for", uses_token_stream(do_for))
    697651
    698 def do_ifequal(parser, token, negate):
    699     bits = list(token.split_contents())
    700     if len(bits) != 3:
    701         raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
    702     end_tag = 'end' + bits[0]
    703     nodelist_true = parser.parse(('else', end_tag))
    704     token = parser.next_token()
    705     if token.contents == 'else':
    706         nodelist_false = parser.parse((end_tag,))
    707         parser.delete_first_token()
    708     else:
    709         nodelist_false = NodeList()
    710     val1 = parser.compile_filter(bits[1])
    711     val2 = parser.compile_filter(bits[2])
    712     return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)
    713 
    714652#@register.tag
    715 def ifequal(parser, token):
     653#@uses_token_stream
     654def ifequal(parser, bits):
    716655    """
    717656    Outputs the contents of the block if the two arguments equal each other.
    718657
     
    728667            ...
    729668        {% endifnotequal %}
    730669    """
    731     return do_ifequal(parser, token, False)
    732 ifequal = register.tag(ifequal)
     670    exp1, exp2 = bits.parse_expression_list(count=2)
     671    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
     672    return IfEqualNode(exp1, exp2, nodelist_true, nodelist_false, False)   
     673ifequal = register.tag(uses_token_stream(ifequal))
    733674
    734675#@register.tag
    735 def ifnotequal(parser, token):
     676#@uses_token_stream
     677def ifnotequal(parser, bits):
    736678    """
    737679    Outputs the contents of the block if the two arguments are not equal.
    738680    See ifequal.
    739681    """
    740     return do_ifequal(parser, token, True)
    741 ifnotequal = register.tag(ifnotequal)
     682    exp1, exp2 = bits.parse_expression_list(count=2)
     683    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name)
     684    return IfEqualNode(exp1, exp2, nodelist_true, nodelist_false, True)   
     685ifnotequal = register.tag(uses_token_stream(ifnotequal))
    742686
    743687#@register.tag(name="if")
    744 def do_if(parser, token):
     688#@uses_token_stream
     689def do_if(parser, bits):
    745690    """
    746691    The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
    747692    (i.e., exists, is not empty, and is not a false boolean value), the
     
    799744            {% endif %}
    800745        {% endif %}
    801746    """
    802     bits = token.contents.split()
    803     del bits[0]
    804     if not bits:
    805         raise TemplateSyntaxError("'if' statement requires at least one argument")
    806     # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
    807     bitstr = ' '.join(bits)
    808     boolpairs = bitstr.split(' and ')
    809747    boolvars = []
    810     if len(boolpairs) == 1:
    811         link_type = IfNode.LinkTypes.or_
    812         boolpairs = bitstr.split(' or ')
    813     else:
    814         link_type = IfNode.LinkTypes.and_
    815         if ' or ' in bitstr:
     748    link_type = None
     749    while True:
     750        negate = bits.pop_lexeme('not')
     751        try:
     752            boolvars.append((negate, bits.parse_expression()))
     753        except TokenSyntaxError:
     754            # If the variable is named "not" we have been too greedy
     755            if negate:
     756                boolvars.append((False, parse_lookup('not')))
     757            else:
     758                bits.expected('<expression>')
     759        link = bits.pop_name()
     760        if not link:
     761            break
     762        if link not in ('and', 'or'):
     763            bits.pushback()
     764            break
     765        if link_type and link != link_type:
    816766            raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
    817     for boolpair in boolpairs:
    818         if ' ' in boolpair:
    819             try:
    820                 not_, boolvar = boolpair.split()
    821             except ValueError:
    822                 raise TemplateSyntaxError, "'if' statement improperly formatted"
    823             if not_ != 'not':
    824                 raise TemplateSyntaxError, "Expected 'not' in if statement"
    825             boolvars.append((True, parser.compile_filter(boolvar)))
    826767        else:
    827             boolvars.append((False, parser.compile_filter(boolpair)))
    828     nodelist_true = parser.parse(('else', 'endif'))
    829     token = parser.next_token()
    830     if token.contents == 'else':
    831         nodelist_false = parser.parse(('endif',))
    832         parser.delete_first_token()
    833     else:
    834         nodelist_false = NodeList()
    835     return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
    836 do_if = register.tag("if", do_if)
     768            link_type = link
     769    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'if')
     770    return IfNode(boolvars, nodelist_true, nodelist_false, link_type == 'and' and IfNode.LinkTypes.and_ or IfNode.LinkTypes.or_)
     771do_if = register.tag("if", uses_token_stream(do_if))
    837772
    838773#@register.tag
    839 def ifchanged(parser, token):
     774#@uses_token_stream
     775def ifchanged(parser, bits):
    840776    """
    841777    Checks if a value has changed from the last iteration of a loop.
    842778
     
    864800                {% endifchanged %}
    865801            {% endfor %}
    866802    """
    867     bits = token.contents.split()
    868     nodelist_true = parser.parse(('else', 'endifchanged'))
    869     token = parser.next_token()
    870     if token.contents == 'else':
    871         nodelist_false = parser.parse(('endifchanged',))
    872         parser.delete_first_token()
    873     else:
    874         nodelist_false = NodeList()
    875     values = [parser.compile_filter(bit) for bit in bits[1:]]
    876     return IfChangedNode(nodelist_true, nodelist_false, *values)
    877 ifchanged = register.tag(ifchanged)
     803    nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'ifchanged')
     804    expressions = bits.parse_expression_list()
     805    return IfChangedNode(nodelist_true, nodelist_false, expressions)
     806ifchanged = register.tag(uses_token_stream(ifchanged))
    878807
    879808#@register.tag
     809#@uses_token_stream
    880810def ssi(parser, token):
    881811    """
    882812    Outputs the contents of a given file into the page.
     
    892822
    893823        {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
    894824    """
    895     bits = token.contents.split()
    896     parsed = False
    897     if len(bits) not in (2, 3):
    898         raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
    899                                   " the file to be included")
    900     if len(bits) == 3:
    901         if bits[2] == 'parsed':
    902             parsed = True
    903         else:
    904             raise TemplateSyntaxError("Second (optional) argument to %s tag"
    905                                       " must be 'parsed'" % bits[0])
    906     return SsiNode(bits[1], parsed)
    907 ssi = register.tag(ssi)
     825    path = bits.parse_string(bare=True, required=True)
     826    parsed = bits.pop_lexeme('parsed')
     827   
     828    return SsiNode(path, parsed)
     829ssi = register.tag(uses_token_stream(ssi))
    908830
    909831#@register.tag
    910832def load(parser, token):
     
    929851load = register.tag(load)
    930852
    931853#@register.tag
    932 def now(parser, token):
     854#@uses_token_stream
     855def now(parser, bits):
    933856    """
    934857    Displays the date, formatted according to the given string.
    935858
     
    940863
    941864        It is {% now "jS F Y H:i" %}
    942865    """
    943     bits = token.contents.split('"')
    944     if len(bits) != 3:
    945         raise TemplateSyntaxError, "'now' statement takes one argument"
    946     format_string = bits[1]
    947     return NowNode(format_string)
    948 now = register.tag(now)
     866    return NowNode(bits.parse_expression(required=True))
     867now = register.tag(uses_token_stream(now))
    949868
    950869#@register.tag
    951 def regroup(parser, token):
     870#@uses_token_stream
     871def regroup(parser, bits):
    952872    """
    953873    Regroups a list of alike objects by a common attribute.
    954874
     
    994914        {% regroup people|dictsort:"gender" by gender as grouped %}
    995915
    996916    """
    997     firstbits = token.contents.split(None, 3)
    998     if len(firstbits) != 4:
    999         raise TemplateSyntaxError, "'regroup' tag takes five arguments"
    1000     target = parser.compile_filter(firstbits[1])
    1001     if firstbits[2] != 'by':
    1002         raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
    1003     lastbits_reversed = firstbits[3][::-1].split(None, 2)
    1004     if lastbits_reversed[1][::-1] != 'as':
    1005         raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
    1006                                   " be 'as'")
    1007 
    1008     expression = parser.compile_filter(lastbits_reversed[2][::-1])
    1009 
    1010     var_name = lastbits_reversed[0][::-1]
     917    target = bits.parse_expression(required=True)
     918    bits.pop_lexeme('by', required=True)
     919    expression = bits.parse_expression(required=True)   
     920    bits.pop_lexeme('as', required=True)
     921    var_name = bits.pop_name(required=True)
    1011922    return RegroupNode(target, expression, var_name)
    1012 regroup = register.tag(regroup)
     923regroup = register.tag(uses_token_stream(regroup))
    1013924
    1014925def spaceless(parser, token):
    1015926    """
     
    1036947            </strong>
    1037948        {% endspaceless %}
    1038949    """
    1039     nodelist = parser.parse(('endspaceless',))
    1040     parser.delete_first_token()
     950    nodelist = parser.parse_nodelist('endspaceless')
    1041951    return SpacelessNode(nodelist)
    1042952spaceless = register.tag(spaceless)
    1043953
     
    1075985    return TemplateTagNode(tag)
    1076986templatetag = register.tag(templatetag)
    1077987
    1078 def url(parser, token):
     988#@register.tag
     989#@uses_token_stream
     990def url(parser, bits):
    1079991    """
    1080992    Returns an absolute URL matching given view with its parameters.
    1081993
     
    11061018
    11071019    The URL will look like ``/clients/client/123/``.
    11081020    """
    1109     bits = token.split_contents()
    1110     if len(bits) < 2:
    1111         raise TemplateSyntaxError("'%s' takes at least one argument"
    1112                                   " (path to a view)" % bits[0])
    1113     viewname = bits[1]
    1114     args = []
    1115     kwargs = {}
    1116     asvar = None
    1117 
    1118     if len(bits) > 2:
    1119         bits = iter(bits[2:])
    1120         for bit in bits:
    1121             if bit == 'as':
    1122                 asvar = bits.next()
    1123                 break
    1124             else:
    1125                 for arg in bit.split(","):
    1126                     if '=' in arg:
    1127                         k, v = arg.split('=', 1)
    1128                         k = k.strip()
    1129                         kwargs[k] = parser.compile_filter(v)
    1130                     elif arg:
    1131                         args.append(parser.compile_filter(arg))
     1021    viewname = bits.parse_string(bare=True, required=True)
     1022    if bits.pop_lexeme(':'):
     1023        viewname = "%s:%s" % (viewname, bits.parse_string(bare=True, required=True))
     1024    args, kwargs = parse_args_and_kwargs(bits, until=('as',))
     1025    asvar = parse_as(bits)
    11321026    return URLNode(viewname, args, kwargs, asvar)
    1133 url = register.tag(url)
     1027url = register.tag(uses_token_stream(url))
    11341028
    11351029#@register.tag
    1136 def widthratio(parser, token):
     1030#@uses_token_stream
     1031def widthratio(parser, bits):
    11371032    """
    11381033    For creating bar charts and such, this tag calculates the ratio of a given
    11391034    value to a maximum value, and then applies that ratio to a constant.
     
    11461041    the above example will be 88 pixels wide (because 175/200 = .875;
    11471042    .875 * 100 = 87.5 which is rounded up to 88).
    11481043    """
    1149     bits = token.contents.split()
    1150     if len(bits) != 4:
    1151         raise TemplateSyntaxError("widthratio takes three arguments")
    1152     tag, this_value_expr, max_value_expr, max_width = bits
     1044    this_value_expr, max_value_expr, max_width = bits.parse_expression_list(count=3)
    11531045
    1154     return WidthRatioNode(parser.compile_filter(this_value_expr),
    1155                           parser.compile_filter(max_value_expr),
    1156                           parser.compile_filter(max_width))
    1157 widthratio = register.tag(widthratio)
     1046    return WidthRatioNode(this_value_expr, max_value_expr, max_width)
     1047widthratio = register.tag(uses_token_stream(widthratio))
    11581048
    11591049#@register.tag
    1160 def do_with(parser, token):
     1050#@uses_token_stream
     1051def do_with(parser, bits):
    11611052    """
    11621053    Adds a value to the context (inside of this block) for caching and easy
    11631054    access.
     
    11681059            {{ total }} object{{ total|pluralize }}
    11691060        {% endwith %}
    11701061    """
    1171     bits = list(token.split_contents())
    1172     if len(bits) != 4 or bits[2] != "as":
    1173         raise TemplateSyntaxError("%r expected format is 'value as name'" %
    1174                                   bits[0])
    1175     var = parser.compile_filter(bits[1])
    1176     name = bits[3]
    1177     nodelist = parser.parse(('endwith',))
    1178     parser.delete_first_token()
    1179     return WithNode(var, name, nodelist)
    1180 do_with = register.tag('with', do_with)
     1062    expression = bits.parse_expression(required=True)
     1063    name = parse_as(bits, required=True)
     1064    nodelist = parser.parse_nodelist('endwith')
     1065    return WithNode(expression, name, nodelist)
     1066do_with = register.tag('with', uses_token_stream(do_with))
  • django/template/compat.py

     
     1import warnings
     2from django.template.expressions import Expression, LookupError
     3from django.template.nodes import ExpressionNode
     4
     5VariableDoesNotExist = LookupError
     6VariableNode = ExpressionNode
     7
     8class Variable(Expression):
     9    def __init__(self, var):
     10        warnings.warn('Use Lookup instead of Variable.', DeprecationWarning, stacklevel=2)   
     11        self.var = var
     12        from django.template.parser import TokenStream, TemplateSyntaxError
     13        stream = TokenStream(None, var)
     14        self.expression = stream.parse_value()
     15        if not stream.consumed():
     16            raise TemplateSyntaxError("Invalid variable: %s" % var)
     17
     18    def resolve(self, context):
     19        return self.expression.resolve(context)
     20
     21    def __repr__(self):
     22        return "<%s: %r>" % (self.__class__.__name__, self.var)
     23
     24    def __str__(self):
     25        return self.var   
     26
     27def resolve_variable(path, context):
     28    """
     29    Returns the resolved variable, which may contain attribute syntax, within
     30    the given context.
     31
     32    Deprecated.
     33    """
     34    warnings.warn('Use Lookup instead of resolve_variable.', DeprecationWarning, stacklevel=2)
     35    from django.template.parser import TokenStream, TemplateSyntaxError
     36    stream = TokenStream(None, path)
     37    val = stream.parse_value()
     38    if not stream.consumed():
     39        raise TemplateSyntaxError("Invalid variable: %s" % path)   
     40    return val.resolve(context)
     41
     42class TokenParser(object):
     43    """
     44    Subclass this and implement the top() method to parse a template line. When
     45    instantiating the parser, pass in the line from the Django template parser.
     46
     47    The parser's "tagname" instance-variable stores the name of the tag that
     48    the filter was called with.
     49    """
     50    def __init__(self, subject):
     51        self.subject = subject
     52        self.pointer = 0
     53        self.backout = []
     54        self.tagname = self.tag()
     55
     56    def top(self):
     57        "Overload this method to do the actual parsing and return the result."
     58        raise NotImplementedError()
     59
     60    def more(self):
     61        "Returns True if there is more stuff in the tag."
     62        return self.pointer < len(self.subject)
     63
     64    def back(self):
     65        "Undoes the last microparser. Use this for lookahead and backtracking."
     66        if not len(self.backout):
     67            # avoid cyclic imports
     68            from django.template import TemplateSyntaxError
     69            raise TemplateSyntaxError("back called without some previous parsing")
     70        self.pointer = self.backout.pop()
     71
     72    def tag(self):
     73        "A microparser that just returns the next tag from the line."
     74        subject = self.subject
     75        i = self.pointer
     76        if i >= len(subject):
     77            # avoid cyclic imports
     78            from django.template import TemplateSyntaxError       
     79            raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)
     80        p = i
     81        while i < len(subject) and subject[i] not in (' ', '\t'):
     82            i += 1
     83        s = subject[p:i]
     84        while i < len(subject) and subject[i] in (' ', '\t'):
     85            i += 1
     86        self.backout.append(self.pointer)
     87        self.pointer = i
     88        return s
     89
     90    def value(self):
     91        "A microparser that parses for a value: some string constant or variable name."
     92        subject = self.subject
     93        i = self.pointer
     94        if i >= len(subject):
     95            # avoid cyclic imports
     96            from django.template import TemplateSyntaxError       
     97            raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)
     98        if subject[i] in ('"', "'"):
     99            p = i
     100            i += 1
     101            while i < len(subject) and subject[i] != subject[p]:
     102                i += 1
     103            if i >= len(subject):
     104                # avoid cyclic imports
     105                from django.template import TemplateSyntaxError           
     106                raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
     107            i += 1
     108            res = subject[p:i]
     109            while i < len(subject) and subject[i] in (' ', '\t'):
     110                i += 1
     111            self.backout.append(self.pointer)
     112            self.pointer = i
     113            return res
     114        else:
     115            p = i
     116            while i < len(subject) and subject[i] not in (' ', '\t'):
     117                if subject[i] in ('"', "'"):
     118                    c = subject[i]
     119                    i += 1
     120                    while i < len(subject) and subject[i] != c:
     121                        i += 1
     122                    if i >= len(subject):
     123                        # avoid cyclic imports
     124                        from django.template import TemplateSyntaxError                   
     125                        raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))
     126                i += 1
     127            s = subject[p:i]
     128            while i < len(subject) and subject[i] in (' ', '\t'):
     129                i += 1
     130            self.backout.append(self.pointer)
     131            self.pointer = i
     132            return s
  • django/template/library.py

     
     1from inspect import getargspec
     2from django.template.context import Context
     3from django.template.nodes import Node
     4from django.template.utils import resolve_list
     5from django.utils.importlib import import_module
     6from django.utils.itercompat import is_iterable
     7from django.utils.functional import curry
     8
     9# global dictionary of libraries that have been loaded using get_library
     10libraries = {}
     11# global list of libraries to load by default for a new parser
     12builtins = []
     13
     14class InvalidTemplateLibrary(Exception):
     15    pass
     16
     17def generic_tag_compiler(params, defaults, name, node_class, parser, bits):
     18    "Returns a template.Node subclass."
     19    bmax = len(params)
     20    def_len = defaults and len(defaults) or 0
     21    bmin = bmax - def_len
     22    expressions = bits.parse_expression_list(minimum=bmin, maximum=bmax)
     23    return node_class(expressions)
     24
     25class Library(object):
     26    def __init__(self):
     27        self.filters = {}
     28        self.tags = {}
     29
     30    def tag(self, name=None, compile_function=None):
     31        if name == None and compile_function == None:
     32            # @register.tag()
     33            return self.tag_function
     34        elif name != None and compile_function == None:
     35            if(callable(name)):
     36                # @register.tag
     37                return self.tag_function(name)
     38            else:
     39                # @register.tag('somename') or @register.tag(name='somename')
     40                def dec(func):
     41                    return self.tag(name, func)
     42                return dec
     43        elif name != None and compile_function != None:
     44            # register.tag('somename', somefunc)
     45            self.tags[name] = compile_function
     46            return compile_function
     47        else:
     48            raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))
     49
     50    def tag_function(self,func):
     51        self.tags[getattr(func, "_decorated_function", func).__name__] = func
     52        return func
     53
     54    def filter(self, name=None, filter_func=None):
     55        if name == None and filter_func == None:
     56            # @register.filter()
     57            return self.filter_function
     58        elif filter_func == None:
     59            if(callable(name)):
     60                # @register.filter
     61                return self.filter_function(name)
     62            else:
     63                # @register.filter('somename') or @register.filter(name='somename')
     64                def dec(func):
     65                    return self.filter(name, func)
     66                return dec
     67        elif name != None and filter_func != None:
     68            # register.filter('somename', somefunc)
     69            self.filters[name] = filter_func
     70            return filter_func
     71        else:
     72            raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))
     73
     74    def filter_function(self, func):
     75        self.filters[getattr(func, "_decorated_function", func).__name__] = func
     76        return func
     77
     78    def simple_tag(self,func):
     79        params, xx, xxx, defaults = getargspec(func)
     80
     81        class SimpleNode(Node):
     82            def __init__(self, args):
     83                self.args = args
     84
     85            def render(self, context):               
     86                return func(*resolve_list(self.args, context))
     87
     88        from django.template.parser import uses_token_stream
     89        compile_func = uses_token_stream(curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode))
     90        compile_func.__doc__ = func.__doc__
     91        self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
     92        return func
     93
     94    def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
     95        def dec(func):
     96            params, xx, xxx, defaults = getargspec(func)
     97            if takes_context:
     98                if params[0] == 'context':
     99                    params = params[1:]
     100                else:
     101                    raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")
     102
     103            class InclusionNode(Node):
     104                def __init__(self, args):
     105                    self.args = args
     106
     107                def render(self, context):
     108                    args = resolve_list(self.args, context)
     109                    if takes_context:
     110                        args = [context] + args
     111                    context_dict = func(*args)
     112
     113                    if not getattr(self, 'nodelist', False):
     114                        from django.template.loader import get_template, select_template
     115                        if not isinstance(file_name, basestring) and is_iterable(file_name):
     116                            t = select_template(file_name)
     117                        else:
     118                            t = get_template(file_name)
     119                        self.nodelist = t.nodelist
     120                    return self.nodelist.render(context_class(context_dict, autoescape=context.autoescape))
     121
     122            from django.template.parser import uses_token_stream
     123            compile_func = uses_token_stream(curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode))
     124            compile_func.__doc__ = func.__doc__
     125            self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
     126            return func
     127        return dec
     128
     129def get_library(module_name):
     130    lib = libraries.get(module_name, None)
     131    if not lib:
     132        try:
     133            mod = import_module(module_name)
     134        except ImportError, e:
     135            raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))
     136        try:
     137            lib = mod.register
     138            libraries[module_name] = lib
     139        except AttributeError:
     140            raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)
     141    return lib
     142
     143def add_to_builtins(module_name, lazy=False):
     144    builtins.append(lazy and module_name or get_library(module_name))
     145   
     146def get_builtins():
     147    for index, lib in enumerate(builtins):
     148        if isinstance(lib, str):
     149            builtins[index] = get_library(lib)
     150        yield builtins[index]
     151
     152add_to_builtins('django.template.defaulttags', True)
     153add_to_builtins('django.template.defaultfilters', True)
     154
  • django/template/loader_tags.py

     
    1 from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
    21from django.template import Library, Node, TextNode
    3 from django.template.loader import get_template, get_template_from_string, find_template_source
     2from django.template.loader import get_template, get_template_from_string, find_template_source, TemplateDoesNotExist
     3from django.template.parser import TemplateSyntaxError, uses_token_stream
     4from django.template.expressions import Literal
    45from django.conf import settings
    56from django.utils.safestring import mark_safe
    67
     
    3940class ExtendsNode(Node):
    4041    must_be_first = True
    4142
    42     def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
     43    def __init__(self, nodelist, parent_name_expr, template_dirs=None):
    4344        self.nodelist = nodelist
    44         self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
     45        self.parent_name_expr = parent_name_expr
    4546        self.template_dirs = template_dirs
    4647
    4748    def __repr__(self):
    48         if self.parent_name_expr:
    49             return "<ExtendsNode: extends %s>" % self.parent_name_expr.token
    50         return '<ExtendsNode: extends "%s">' % self.parent_name
     49        return "<ExtendsNode: extends %s>" % self.parent_name_expr
    5150
    5251    def get_parent(self, context):
    53         if self.parent_name_expr:
    54             self.parent_name = self.parent_name_expr.resolve(context)
    55         parent = self.parent_name
     52        parent = self.parent_name_expr.resolve_safe(context)
    5653        if not parent:
    57             error_msg = "Invalid template name in 'extends' tag: %r." % parent
    58             if self.parent_name_expr:
    59                 error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token
     54            error_msg = "Invalid template name in 'extends' tag: %r.  Got this from '%s'." % (parent, self.parent_name_expr)
    6055            raise TemplateSyntaxError, error_msg
    6156        if hasattr(parent, 'render'):
    6257            return parent # parent is a Template object
     
    114109
    115110class IncludeNode(Node):
    116111    def __init__(self, template_name):
    117         self.template_name = Variable(template_name)
     112        self.template_name = template_name
    118113
    119114    def render(self, context):
    120115        try:
     
    148143    parser.delete_first_token()
    149144    return BlockNode(block_name, nodelist)
    150145
    151 def do_extends(parser, token):
     146#@uses_token_stream
     147def do_extends(parser, bits):
    152148    """
    153149    Signal that this template extends a parent template.
    154150
     
    158154    name of the parent template to extend (if it evaluates to a string) or as
    159155    the parent tempate itelf (if it evaluates to a Template object).
    160156    """
    161     bits = token.split_contents()
    162     if len(bits) != 2:
    163         raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
    164     parent_name, parent_name_expr = None, None
    165     if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
    166         parent_name = bits[1][1:-1]
    167     else:
    168         parent_name_expr = parser.compile_filter(bits[1])
     157    parent_name_expr = bits.parse_expression()
    169158    nodelist = parser.parse()
    170159    if nodelist.get_nodes_by_type(ExtendsNode):
    171160        raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0]
    172     return ExtendsNode(nodelist, parent_name, parent_name_expr)
     161    return ExtendsNode(nodelist, parent_name_expr)
    173162
    174 def do_include(parser, token):
     163#@uses_token_stream
     164def do_include(parser, bits):
    175165    """
    176166    Loads a template and renders it with the current context.
    177167
     
    179169
    180170        {% include "foo/some_include" %}
    181171    """
    182     bits = token.split_contents()
    183     if len(bits) != 2:
    184         raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
    185     path = bits[1]
    186     if path[0] in ('"', "'") and path[-1] == path[0]:
    187         return ConstantIncludeNode(path[1:-1])
    188     return IncludeNode(bits[1])
     172    expression = bits.parse_expression(required=True)
     173    if isinstance(expression, Literal):
     174        return ConstantIncludeNode(expression.value)
     175    return IncludeNode(expression)
    189176
    190177register.tag('block', do_block)
    191 register.tag('extends', do_extends)
    192 register.tag('include', do_include)
     178register.tag('extends', uses_token_stream(do_extends))
     179register.tag('include', uses_token_stream(do_include))
  • django/template/loader.py

     
    2121# installed, because pkg_resources is necessary to read eggs.
    2222
    2323from django.core.exceptions import ImproperlyConfigured
    24 from django.template import Origin, Template, Context, TemplateDoesNotExist, add_to_builtins
     24from django.template.parser import Origin, Template
     25from django.template.library import add_to_builtins
     26from django.template.context import Context
    2527from django.utils.importlib import import_module
    2628from django.conf import settings
    2729
    2830template_source_loaders = None
    2931
     32class TemplateDoesNotExist(Exception):
     33    pass
     34
    3035class LoaderOrigin(Origin):
    3136    def __init__(self, display_name, loader, name, dirs):
    3237        super(LoaderOrigin, self).__init__(display_name)
     
    117122    # If we get here, none of the templates could be loaded
    118123    raise TemplateDoesNotExist, ', '.join(template_name_list)
    119124
    120 add_to_builtins('django.template.loader_tags')
     125add_to_builtins('django.template.loader_tags', True)
     126
  • django/template/debug.py

     
    1 from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError
     1from django.template import Lexer, Parser, NodeList, ExpressionNode, TemplateSyntaxError
     2from django.template.parser import tag_re
    23from django.utils.encoding import force_unicode
    34from django.utils.html import escape
    45from django.utils.safestring import SafeData, EscapeData
     
    5051        return DebugNodeList()
    5152
    5253    def create_variable_node(self, contents):
    53         return DebugVariableNode(contents)
     54        return DebugExpressionNode(contents)
    5455
    5556    def extend_nodelist(self, nodelist, node, token):
    5657        node.source = token.source
     
    8182            raise wrapped
    8283        return result
    8384
    84 class DebugVariableNode(VariableNode):
     85class DebugExpressionNode(ExpressionNode):
    8586    def render(self, context):
    8687        try:
    87             output = force_unicode(self.filter_expression.resolve(context))
     88            return super(DebugExpressionNode, self).render(context)
    8889        except TemplateSyntaxError, e:
    8990            if not hasattr(e, 'source'):
    9091                e.source = self.source
    9192            raise
    9293        except UnicodeDecodeError:
    9394            return ''
    94         if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
    95             return escape(output)
    96         else:
    97             return output
  • tests/regressiontests/templates/parser.py

     
    33"""
    44
    55filter_parsing = r"""
    6 >>> from django.template import FilterExpression, Parser
     6>>> from django.template import Parser
    77
    88>>> c = {'article': {'section': u'News'}}
    99>>> p = Parser("")
    10 >>> def fe_test(s): return FilterExpression(s, p).resolve(c)
     10>>> def fe_test(s): return p.compile_filter(s).resolve(c)
    1111
    1212>>> fe_test('article.section')
    1313u'News'
     
    2222>>> fe_test(ur"'Some \'Bad\' News'")
    2323u"Some 'Bad' News"
    2424
    25 >>> fe = FilterExpression(ur'"Some \"Good\" News"', p)
    26 >>> fe.filters
    27 []
    28 >>> fe.var
    29 u'Some "Good" News'
    3025"""
    3126
    3227variable_parsing = r"""
  • tests/regressiontests/templates/tests.py

     
    370370            # Chained filters
    371371            'filter-syntax02': ("{{ var|upper|lower }}", {"var": "Django is the greatest!"}, "django is the greatest!"),
    372372
    373             # Raise TemplateSyntaxError for space between a variable and filter pipe
    374             'filter-syntax03': ("{{ var |upper }}", {}, template.TemplateSyntaxError),
     373            # Basic filter usage with space between a variable and filter pipe
     374            'filter-syntax03': ("{{ var |upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
    375375
    376             # Raise TemplateSyntaxError for space after a filter pipe
    377             'filter-syntax04': ("{{ var| upper }}", {}, template.TemplateSyntaxError),
     376            # Basic filter usage with space after a filter pipe
     377            'filter-syntax04': ("{{ var| upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
    378378
    379379            # Raise TemplateSyntaxError for a nonexistent filter
    380380            'filter-syntax05': ("{{ var|does_not_exist }}", {}, template.TemplateSyntaxError),
Back to Top