Django

Code

root/django/trunk/django/template/__init__.py

Revision 10519, 35.8 kB (checked in by mtredinnick, 3 months ago)

Fixed #10369 -- Fixed auto-escaping inside "tran" and "blocktrans" tags.

Patch from Andrew Badr.

  • Property svn:eol-style set to native
  • Property svn:keywords set to LastChangedRevision
Line 
1 """
2 This is the Django template system.
3
4 How it works:
5
6 The Lexer.tokenize() function converts a template string (i.e., a string containing
7 markup with custom template tags) to tokens, which can be either plain text
8 (TOKEN_TEXT), variables (TOKEN_VAR) or block statements (TOKEN_BLOCK).
9
10 The Parser() class takes a list of tokens in its constructor, and its parse()
11 method returns a compiled template -- which is, under the hood, a list of
12 Node objects.
13
14 Each Node is responsible for creating some sort of output -- e.g. simple text
15 (TextNode), variable values in a given context (VariableNode), results of basic
16 logic (IfNode), results of looping (ForNode), or anything else. The core Node
17 types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can
18 define their own custom node types.
19
20 Each Node has a render() method, which takes a Context and returns a string of
21 the rendered node. For example, the render() method of a Variable Node returns
22 the variable's value as a string. The render() method of an IfNode returns the
23 rendered output of whatever was inside the loop, recursively.
24
25 The Template class is a convenient wrapper that takes care of template
26 compilation and rendering.
27
28 Usage:
29
30 The only thing you should ever use directly in this file is the Template class.
31 Create a compiled template object with a template_string, then call render()
32 with a context. In the compilation stage, the TemplateSyntaxError exception
33 will be raised if the template doesn't have proper syntax.
34
35 Sample code:
36
37 >>> from django import template
38 >>> s = u'<html>{% if test %}<h1>{{ varvalue }}</h1>{% endif %}</html>'
39 >>> t = template.Template(s)
40
41 (t is now a compiled template, and its render() method can be called multiple
42 times with multiple contexts)
43
44 >>> c = template.Context({'test':True, 'varvalue': 'Hello'})
45 >>> t.render(c)
46 u'<html><h1>Hello</h1></html>'
47 >>> c = template.Context({'test':False, 'varvalue': 'Hello'})
48 >>> t.render(c)
49 u'<html></html>'
50 """
51 import re
52 from inspect import getargspec
53
54 from django.conf import settings
55 from 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
64
65 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
66
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
101 # True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means
102 # uninitialised.
103 invalid_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')
Note: See TracBrowser for help on using the browser.