App Engine Python SDK version 1.9.12
[gae.git] / python / lib / django-0.96 / django / template / __init__.py
blob0d8990a42b61273588ff318667c2ea1a701fe573
1 """
2 This is the Django template system.
4 How it works:
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).
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.
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.
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.
25 The Template class is a convenient wrapper that takes care of template
26 compilation and rendering.
28 Usage:
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.
35 Sample code:
37 >>> import template
38 >>> s = '''
39 ... <html>
40 ... {% if test %}
41 ... <h1>{{ varvalue }}</h1>
42 ... {% endif %}
43 ... </html>
44 ... '''
45 >>> t = template.Template(s)
47 (t is now a compiled template, and its render() method can be called multiple
48 times with multiple contexts)
50 >>> c = template.Context({'test':True, 'varvalue': 'Hello'})
51 >>> t.render(c)
52 '\n<html>\n\n <h1>Hello</h1>\n\n</html>\n'
53 >>> c = template.Context({'test':False, 'varvalue': 'Hello'})
54 >>> t.render(c)
55 '\n<html>\n\n</html>\n'
56 """
57 import re
58 from inspect import getargspec
59 from django.conf import settings
60 from django.template.context import Context, RequestContext, ContextPopException
61 from django.utils.functional import curry
62 from django.utils.text import smart_split
64 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
66 TOKEN_TEXT = 0
67 TOKEN_VAR = 1
68 TOKEN_BLOCK = 2
69 TOKEN_COMMENT = 3
71 # template syntax constants
72 FILTER_SEPARATOR = '|'
73 FILTER_ARGUMENT_SEPARATOR = ':'
74 VARIABLE_ATTRIBUTE_SEPARATOR = '.'
75 BLOCK_TAG_START = '{%'
76 BLOCK_TAG_END = '%}'
77 VARIABLE_TAG_START = '{{'
78 VARIABLE_TAG_END = '}}'
79 COMMENT_TAG_START = '{#'
80 COMMENT_TAG_END = '#}'
81 SINGLE_BRACE_START = '{'
82 SINGLE_BRACE_END = '}'
84 ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
86 # what to report as the origin for templates that come from non-loader sources
87 # (e.g. strings)
88 UNKNOWN_SOURCE="&lt;unknown source&gt;"
90 # match a variable or block tag and capture the entire tag, including start/end delimiters
91 tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),
92 re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
93 re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))
94 # matches if the string is valid number
95 number_re = re.compile(r'[-+]?(\d+|\d*\.\d+)$')
97 # global dictionary of libraries that have been loaded using get_library
98 libraries = {}
99 # global list of libraries to load by default for a new parser
100 builtins = []
102 class TemplateSyntaxError(Exception):
103 def __str__(self):
104 try:
105 import cStringIO as StringIO
106 except ImportError:
107 import StringIO
108 output = StringIO.StringIO()
109 output.write(Exception.__str__(self))
110 # Check if we wrapped an exception and print that too.
111 if hasattr(self, 'exc_info'):
112 import traceback
113 output.write('\n\nOriginal ')
114 e = self.exc_info
115 traceback.print_exception(e[0], e[1], e[2], 500, output)
116 return output.getvalue()
118 class TemplateDoesNotExist(Exception):
119 pass
121 class VariableDoesNotExist(Exception):
123 def __init__(self, msg, params=()):
124 self.msg = msg
125 self.params = params
127 def __str__(self):
128 return self.msg % self.params
130 class InvalidTemplateLibrary(Exception):
131 pass
133 class Origin(object):
134 def __init__(self, name):
135 self.name = name
137 def reload(self):
138 raise NotImplementedError
140 def __str__(self):
141 return self.name
143 class StringOrigin(Origin):
144 def __init__(self, source):
145 super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
146 self.source = source
148 def reload(self):
149 return self.source
151 class Template(object):
152 def __init__(self, template_string, origin=None, name='<Unknown Template>'):
153 "Compilation stage"
154 if settings.TEMPLATE_DEBUG and origin == None:
155 origin = StringOrigin(template_string)
156 # Could do some crazy stack-frame stuff to record where this string
157 # came from...
158 self.nodelist = compile_string(template_string, origin)
159 self.name = name
161 def __iter__(self):
162 for node in self.nodelist:
163 for subnode in node:
164 yield subnode
166 def render(self, context):
167 "Display stage -- can be called many times"
168 return self.nodelist.render(context)
170 def compile_string(template_string, origin):
171 "Compiles template_string into NodeList ready for rendering"
172 lexer = lexer_factory(template_string, origin)
173 parser = parser_factory(lexer.tokenize())
174 return parser.parse()
176 class Token(object):
177 def __init__(self, token_type, contents):
178 "The token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT"
179 self.token_type, self.contents = token_type, contents
181 def __str__(self):
182 return '<%s token: "%s...">' % \
183 ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
184 self.contents[:20].replace('\n', ''))
186 def split_contents(self):
187 return list(smart_split(self.contents))
189 class Lexer(object):
190 def __init__(self, template_string, origin):
191 self.template_string = template_string
192 self.origin = origin
194 def tokenize(self):
195 "Return a list of tokens from a given template_string"
196 # remove all empty strings, because the regex has a tendency to add them
197 bits = filter(None, tag_re.split(self.template_string))
198 return map(self.create_token, bits)
200 def create_token(self,token_string):
201 "Convert the given token string into a new Token object and return it"
202 if token_string.startswith(VARIABLE_TAG_START):
203 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
204 elif token_string.startswith(BLOCK_TAG_START):
205 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
206 elif token_string.startswith(COMMENT_TAG_START):
207 token = Token(TOKEN_COMMENT, '')
208 else:
209 token = Token(TOKEN_TEXT, token_string)
210 return token
212 class DebugLexer(Lexer):
213 def __init__(self, template_string, origin):
214 super(DebugLexer, self).__init__(template_string, origin)
216 def tokenize(self):
217 "Return a list of tokens from a given template_string"
218 token_tups, upto = [], 0
219 for match in tag_re.finditer(self.template_string):
220 start, end = match.span()
221 if start > upto:
222 token_tups.append( (self.template_string[upto:start], (upto, start)) )
223 upto = start
224 token_tups.append( (self.template_string[start:end], (start,end)) )
225 upto = end
226 last_bit = self.template_string[upto:]
227 if last_bit:
228 token_tups.append( (last_bit, (upto, upto + len(last_bit))) )
229 return [self.create_token(tok, (self.origin, loc)) for tok, loc in token_tups]
231 def create_token(self, token_string, source):
232 token = super(DebugLexer, self).create_token(token_string)
233 token.source = source
234 return token
236 class Parser(object):
237 def __init__(self, tokens):
238 self.tokens = tokens
239 self.tags = {}
240 self.filters = {}
241 for lib in builtins:
242 self.add_library(lib)
244 def parse(self, parse_until=None):
245 if parse_until is None: parse_until = []
246 nodelist = self.create_nodelist()
247 while self.tokens:
248 token = self.next_token()
249 if token.token_type == TOKEN_TEXT:
250 self.extend_nodelist(nodelist, TextNode(token.contents), token)
251 elif token.token_type == TOKEN_VAR:
252 if not token.contents:
253 self.empty_variable(token)
254 filter_expression = self.compile_filter(token.contents)
255 var_node = self.create_variable_node(filter_expression)
256 self.extend_nodelist(nodelist, var_node,token)
257 elif token.token_type == TOKEN_BLOCK:
258 if token.contents in parse_until:
259 # put token back on token list so calling code knows why it terminated
260 self.prepend_token(token)
261 return nodelist
262 try:
263 command = token.contents.split()[0]
264 except IndexError:
265 self.empty_block_tag(token)
266 # execute callback function for this tag and append resulting node
267 self.enter_command(command, token)
268 try:
269 compile_func = self.tags[command]
270 except KeyError:
271 self.invalid_block_tag(token, command)
272 try:
273 compiled_result = compile_func(self, token)
274 except TemplateSyntaxError, e:
275 if not self.compile_function_error(token, e):
276 raise
277 self.extend_nodelist(nodelist, compiled_result, token)
278 self.exit_command()
279 if parse_until:
280 self.unclosed_block_tag(parse_until)
281 return nodelist
283 def skip_past(self, endtag):
284 while self.tokens:
285 token = self.next_token()
286 if token.token_type == TOKEN_BLOCK and token.contents == endtag:
287 return
288 self.unclosed_block_tag([endtag])
290 def create_variable_node(self, filter_expression):
291 return VariableNode(filter_expression)
293 def create_nodelist(self):
294 return NodeList()
296 def extend_nodelist(self, nodelist, node, token):
297 nodelist.append(node)
299 def enter_command(self, command, token):
300 pass
302 def exit_command(self):
303 pass
305 def error(self, token, msg ):
306 return TemplateSyntaxError(msg)
308 def empty_variable(self, token):
309 raise self.error( token, "Empty variable tag")
311 def empty_block_tag(self, token):
312 raise self.error( token, "Empty block tag")
314 def invalid_block_tag(self, token, command):
315 raise self.error( token, "Invalid block tag: '%s'" % command)
317 def unclosed_block_tag(self, parse_until):
318 raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until))
320 def compile_function_error(self, token, e):
321 pass
323 def next_token(self):
324 return self.tokens.pop(0)
326 def prepend_token(self, token):
327 self.tokens.insert(0, token)
329 def delete_first_token(self):
330 del self.tokens[0]
332 def add_library(self, lib):
333 self.tags.update(lib.tags)
334 self.filters.update(lib.filters)
336 def compile_filter(self, token):
337 "Convenient wrapper for FilterExpression"
338 return FilterExpression(token, self)
340 def find_filter(self, filter_name):
341 if self.filters.has_key(filter_name):
342 return self.filters[filter_name]
343 else:
344 raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name
346 class DebugParser(Parser):
347 def __init__(self, lexer):
348 super(DebugParser, self).__init__(lexer)
349 self.command_stack = []
351 def enter_command(self, command, token):
352 self.command_stack.append( (command, token.source) )
354 def exit_command(self):
355 self.command_stack.pop()
357 def error(self, token, msg):
358 return self.source_error(token.source, msg)
360 def source_error(self, source,msg):
361 e = TemplateSyntaxError(msg)
362 e.source = source
363 return e
365 def create_nodelist(self):
366 return DebugNodeList()
368 def create_variable_node(self, contents):
369 return DebugVariableNode(contents)
371 def extend_nodelist(self, nodelist, node, token):
372 node.source = token.source
373 super(DebugParser, self).extend_nodelist(nodelist, node, token)
375 def unclosed_block_tag(self, parse_until):
376 command, source = self.command_stack.pop()
377 msg = "Unclosed tag '%s'. Looking for one of: %s " % (command, ', '.join(parse_until))
378 raise self.source_error( source, msg)
380 def compile_function_error(self, token, e):
381 if not hasattr(e, 'source'):
382 e.source = token.source
384 def lexer_factory(*args, **kwargs):
385 if settings.TEMPLATE_DEBUG:
386 return DebugLexer(*args, **kwargs)
387 else:
388 return Lexer(*args, **kwargs)
390 def parser_factory(*args, **kwargs):
391 if settings.TEMPLATE_DEBUG:
392 return DebugParser(*args, **kwargs)
393 else:
394 return Parser(*args, **kwargs)
396 class TokenParser(object):
398 Subclass this and implement the top() method to parse a template line. When
399 instantiating the parser, pass in the line from the Django template parser.
401 The parser's "tagname" instance-variable stores the name of the tag that
402 the filter was called with.
404 def __init__(self, subject):
405 self.subject = subject
406 self.pointer = 0
407 self.backout = []
408 self.tagname = self.tag()
410 def top(self):
411 "Overload this method to do the actual parsing and return the result."
412 raise NotImplemented
414 def more(self):
415 "Returns True if there is more stuff in the tag."
416 return self.pointer < len(self.subject)
418 def back(self):
419 "Undoes the last microparser. Use this for lookahead and backtracking."
420 if not len(self.backout):
421 raise TemplateSyntaxError, "back called without some previous parsing"
422 self.pointer = self.backout.pop()
424 def tag(self):
425 "A microparser that just returns the next tag from the line."
426 subject = self.subject
427 i = self.pointer
428 if i >= len(subject):
429 raise TemplateSyntaxError, "expected another tag, found end of string: %s" % subject
430 p = i
431 while i < len(subject) and subject[i] not in (' ', '\t'):
432 i += 1
433 s = subject[p:i]
434 while i < len(subject) and subject[i] in (' ', '\t'):
435 i += 1
436 self.backout.append(self.pointer)
437 self.pointer = i
438 return s
440 def value(self):
441 "A microparser that parses for a value: some string constant or variable name."
442 subject = self.subject
443 i = self.pointer
444 if i >= len(subject):
445 raise TemplateSyntaxError, "Searching for value. Expected another value but found end of string: %s" % subject
446 if subject[i] in ('"', "'"):
447 p = i
448 i += 1
449 while i < len(subject) and subject[i] != subject[p]:
450 i += 1
451 if i >= len(subject):
452 raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % (i, subject)
453 i += 1
454 res = subject[p:i]
455 while i < len(subject) and subject[i] in (' ', '\t'):
456 i += 1
457 self.backout.append(self.pointer)
458 self.pointer = i
459 return res
460 else:
461 p = i
462 while i < len(subject) and subject[i] not in (' ', '\t'):
463 if subject[i] in ('"', "'"):
464 c = subject[i]
465 i += 1
466 while i < len(subject) and subject[i] != c:
467 i += 1
468 if i >= len(subject):
469 raise TemplateSyntaxError, "Searching for value. Unexpected end of string in column %d: %s" % subject
470 i += 1
471 s = subject[p:i]
472 while i < len(subject) and subject[i] in (' ', '\t'):
473 i += 1
474 self.backout.append(self.pointer)
475 self.pointer = i
476 return s
481 filter_raw_string = r"""
482 ^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|
483 ^"(?P<constant>%(str)s)"|
484 ^(?P<var>[%(var_chars)s]+)|
485 (?:%(filter_sep)s
486 (?P<filter_name>\w+)
487 (?:%(arg_sep)s
489 %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
490 "(?P<constant_arg>%(str)s)"|
491 (?P<var_arg>[%(var_chars)s]+)
494 )""" % {
495 'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
496 'var_chars': "A-Za-z0-9\_\." ,
497 'filter_sep': re.escape(FILTER_SEPARATOR),
498 'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
499 'i18n_open' : re.escape("_("),
500 'i18n_close' : re.escape(")"),
503 filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
504 filter_re = re.compile(filter_raw_string)
506 class FilterExpression(object):
508 Parses a variable token and its optional filters (all as a single string),
509 and return a list of tuples of the filter name and arguments.
510 Sample:
511 >>> token = 'variable|default:"Default value"|date:"Y-m-d"'
512 >>> p = FilterParser(token)
513 >>> p.filters
514 [('default', 'Default value'), ('date', 'Y-m-d')]
515 >>> p.var
516 'variable'
518 This class should never be instantiated outside of the
519 get_filters_from_token helper function.
521 def __init__(self, token, parser):
522 self.token = token
523 matches = filter_re.finditer(token)
524 var = None
525 filters = []
526 upto = 0
527 for match in matches:
528 start = match.start()
529 if upto != start:
530 raise TemplateSyntaxError, "Could not parse some characters: %s|%s|%s" % \
531 (token[:upto], token[upto:start], token[start:])
532 if var == None:
533 var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
534 if i18n_constant:
535 var = '"%s"' % _(i18n_constant)
536 elif constant:
537 var = '"%s"' % constant
538 upto = match.end()
539 if var == None:
540 raise TemplateSyntaxError, "Could not find variable at start of %s" % token
541 elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
542 raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % var
543 else:
544 filter_name = match.group("filter_name")
545 args = []
546 constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")
547 if i18n_arg:
548 args.append((False, _(i18n_arg.replace(r'\"', '"'))))
549 elif constant_arg is not None:
550 args.append((False, constant_arg.replace(r'\"', '"')))
551 elif var_arg:
552 args.append((True, var_arg))
553 filter_func = parser.find_filter(filter_name)
554 self.args_check(filter_name,filter_func, args)
555 filters.append( (filter_func,args))
556 upto = match.end()
557 if upto != len(token):
558 raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:]
559 self.var, self.filters = var, filters
561 def resolve(self, context, ignore_failures=False):
562 try:
563 obj = resolve_variable(self.var, context)
564 except VariableDoesNotExist:
565 if ignore_failures:
566 obj = None
567 else:
568 if settings.TEMPLATE_STRING_IF_INVALID:
569 return settings.TEMPLATE_STRING_IF_INVALID
570 else:
571 obj = settings.TEMPLATE_STRING_IF_INVALID
572 for func, args in self.filters:
573 arg_vals = []
574 for lookup, arg in args:
575 if not lookup:
576 arg_vals.append(arg)
577 else:
578 arg_vals.append(resolve_variable(arg, context))
579 obj = func(obj, *arg_vals)
580 return obj
582 def args_check(name, func, provided):
583 provided = list(provided)
584 plen = len(provided)
585 # Check to see if a decorator is providing the real function.
586 func = getattr(func, '_decorated_function', func)
587 args, varargs, varkw, defaults = getargspec(func)
588 # First argument is filter input.
589 args.pop(0)
590 if defaults:
591 nondefs = args[:-len(defaults)]
592 else:
593 nondefs = args
594 # Args without defaults must be provided.
595 try:
596 for arg in nondefs:
597 provided.pop(0)
598 except IndexError:
599 # Not enough
600 raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
602 # Defaults can be overridden.
603 defaults = defaults and list(defaults) or []
604 try:
605 for parg in provided:
606 defaults.pop(0)
607 except IndexError:
608 # Too many.
609 raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen)
611 return True
612 args_check = staticmethod(args_check)
614 def __str__(self):
615 return self.token
617 def resolve_variable(path, context):
619 Returns the resolved variable, which may contain attribute syntax, within
620 the given context. The variable may be a hard-coded string (if it begins
621 and ends with single or double quote marks).
623 >>> c = {'article': {'section':'News'}}
624 >>> resolve_variable('article.section', c)
625 'News'
626 >>> resolve_variable('article', c)
627 {'section': 'News'}
628 >>> class AClass: pass
629 >>> c = AClass()
630 >>> c.article = AClass()
631 >>> c.article.section = 'News'
632 >>> resolve_variable('article.section', c)
633 'News'
635 (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')
637 if number_re.match(path):
638 number_type = '.' in path and float or int
639 current = number_type(path)
640 elif path[0] in ('"', "'") and path[0] == path[-1]:
641 current = path[1:-1]
642 else:
643 current = context
644 bits = path.split(VARIABLE_ATTRIBUTE_SEPARATOR)
645 while bits:
646 try: # dictionary lookup
647 current = current[bits[0]]
648 except (TypeError, AttributeError, KeyError):
649 try: # attribute lookup
650 current = getattr(current, bits[0])
651 if callable(current):
652 if getattr(current, 'alters_data', False):
653 current = settings.TEMPLATE_STRING_IF_INVALID
654 else:
655 try: # method call (assuming no args required)
656 current = current()
657 except TypeError: # arguments *were* required
658 # GOTCHA: This will also catch any TypeError
659 # raised in the function itself.
660 current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
661 except Exception, e:
662 if getattr(e, 'silent_variable_failure', False):
663 current = settings.TEMPLATE_STRING_IF_INVALID
664 else:
665 raise
666 except (TypeError, AttributeError):
667 try: # list-index lookup
668 current = current[int(bits[0])]
669 except (IndexError, # list index out of range
670 ValueError, # invalid literal for int()
671 KeyError, # current is a dict without `int(bits[0])` key
672 TypeError, # unsubscriptable object
674 raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bits[0], current)) # missing attribute
675 except Exception, e:
676 if getattr(e, 'silent_variable_failure', False):
677 current = settings.TEMPLATE_STRING_IF_INVALID
678 else:
679 raise
680 del bits[0]
681 return current
683 class Node(object):
684 def render(self, context):
685 "Return the node rendered as a string"
686 pass
688 def __iter__(self):
689 yield self
691 def get_nodes_by_type(self, nodetype):
692 "Return a list of all nodes (within this node and its nodelist) of the given type"
693 nodes = []
694 if isinstance(self, nodetype):
695 nodes.append(self)
696 if hasattr(self, 'nodelist'):
697 nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
698 return nodes
700 class NodeList(list):
701 def render(self, context):
702 bits = []
703 for node in self:
704 if isinstance(node, Node):
705 bits.append(self.render_node(node, context))
706 else:
707 bits.append(node)
708 return ''.join(bits)
710 def get_nodes_by_type(self, nodetype):
711 "Return a list of all nodes of the given type"
712 nodes = []
713 for node in self:
714 nodes.extend(node.get_nodes_by_type(nodetype))
715 return nodes
717 def render_node(self, node, context):
718 return(node.render(context))
720 class DebugNodeList(NodeList):
721 def render_node(self, node, context):
722 try:
723 result = node.render(context)
724 except TemplateSyntaxError, e:
725 if not hasattr(e, 'source'):
726 e.source = node.source
727 raise
728 except Exception, e:
729 from sys import exc_info
730 wrapped = TemplateSyntaxError('Caught an exception while rendering: %s' % e)
731 wrapped.source = node.source
732 wrapped.exc_info = exc_info()
733 raise wrapped
734 return result
736 class TextNode(Node):
737 def __init__(self, s):
738 self.s = s
740 def __repr__(self):
741 return "<Text Node: '%s'>" % self.s[:25]
743 def render(self, context):
744 return self.s
746 class VariableNode(Node):
747 def __init__(self, filter_expression):
748 self.filter_expression = filter_expression
750 def __repr__(self):
751 return "<Variable Node: %s>" % self.filter_expression
753 def encode_output(self, output):
754 # Check type so that we don't run str() on a Unicode object
755 if not isinstance(output, basestring):
756 try:
757 return str(output)
758 except UnicodeEncodeError:
759 # If __str__() returns a Unicode object, convert it to bytestring.
760 return unicode(output).encode(settings.DEFAULT_CHARSET)
761 elif isinstance(output, unicode):
762 return output.encode(settings.DEFAULT_CHARSET)
763 else:
764 return output
766 def render(self, context):
767 output = self.filter_expression.resolve(context)
768 return self.encode_output(output)
770 class DebugVariableNode(VariableNode):
771 def render(self, context):
772 try:
773 output = self.filter_expression.resolve(context)
774 except TemplateSyntaxError, e:
775 if not hasattr(e, 'source'):
776 e.source = self.source
777 raise
778 return self.encode_output(output)
780 def generic_tag_compiler(params, defaults, name, node_class, parser, token):
781 "Returns a template.Node subclass."
782 bits = token.split_contents()[1:]
783 bmax = len(params)
784 def_len = defaults and len(defaults) or 0
785 bmin = bmax - def_len
786 if(len(bits) < bmin or len(bits) > bmax):
787 if bmin == bmax:
788 message = "%s takes %s arguments" % (name, bmin)
789 else:
790 message = "%s takes between %s and %s arguments" % (name, bmin, bmax)
791 raise TemplateSyntaxError, message
792 return node_class(bits)
794 class Library(object):
795 def __init__(self):
796 self.filters = {}
797 self.tags = {}
799 def tag(self, name=None, compile_function=None):
800 if name == None and compile_function == None:
801 # @register.tag()
802 return self.tag_function
803 elif name != None and compile_function == None:
804 if(callable(name)):
805 # @register.tag
806 return self.tag_function(name)
807 else:
808 # @register.tag('somename') or @register.tag(name='somename')
809 def dec(func):
810 return self.tag(name, func)
811 return dec
812 elif name != None and compile_function != None:
813 # register.tag('somename', somefunc)
814 self.tags[name] = compile_function
815 return compile_function
816 else:
817 raise InvalidTemplateLibrary, "Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)
819 def tag_function(self,func):
820 self.tags[getattr(func, "_decorated_function", func).__name__] = func
821 return func
823 def filter(self, name=None, filter_func=None):
824 if name == None and filter_func == None:
825 # @register.filter()
826 return self.filter_function
827 elif filter_func == None:
828 if(callable(name)):
829 # @register.filter
830 return self.filter_function(name)
831 else:
832 # @register.filter('somename') or @register.filter(name='somename')
833 def dec(func):
834 return self.filter(name, func)
835 return dec
836 elif name != None and filter_func != None:
837 # register.filter('somename', somefunc)
838 self.filters[name] = filter_func
839 return filter_func
840 else:
841 raise InvalidTemplateLibrary, "Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func)
843 def filter_function(self, func):
844 self.filters[getattr(func, "_decorated_function", func).__name__] = func
845 return func
847 def simple_tag(self,func):
848 params, xx, xxx, defaults = getargspec(func)
850 class SimpleNode(Node):
851 def __init__(self, vars_to_resolve):
852 self.vars_to_resolve = vars_to_resolve
854 def render(self, context):
855 resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
856 return func(*resolved_vars)
858 compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)
859 compile_func.__doc__ = func.__doc__
860 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
861 return func
863 def inclusion_tag(self, file_name, context_class=Context, takes_context=False):
864 def dec(func):
865 params, xx, xxx, defaults = getargspec(func)
866 if takes_context:
867 if params[0] == 'context':
868 params = params[1:]
869 else:
870 raise TemplateSyntaxError, "Any tag function decorated with takes_context=True must have a first argument of 'context'"
872 class InclusionNode(Node):
873 def __init__(self, vars_to_resolve):
874 self.vars_to_resolve = vars_to_resolve
876 def render(self, context):
877 resolved_vars = [resolve_variable(var, context) for var in self.vars_to_resolve]
878 if takes_context:
879 args = [context] + resolved_vars
880 else:
881 args = resolved_vars
883 dict = func(*args)
885 if not getattr(self, 'nodelist', False):
886 from django.template.loader import get_template, select_template
887 if hasattr(file_name, '__iter__'):
888 t = select_template(file_name)
889 else:
890 t = get_template(file_name)
891 self.nodelist = t.nodelist
892 return self.nodelist.render(context_class(dict))
894 compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)
895 compile_func.__doc__ = func.__doc__
896 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)
897 return func
898 return dec
900 def get_library(module_name):
901 lib = libraries.get(module_name, None)
902 if not lib:
903 try:
904 mod = __import__(module_name, {}, {}, [''])
905 except ImportError, e:
906 raise InvalidTemplateLibrary, "Could not load template library from %s, %s" % (module_name, e)
907 try:
908 lib = mod.register
909 libraries[module_name] = lib
910 except AttributeError:
911 raise InvalidTemplateLibrary, "Template library %s does not have a variable named 'register'" % module_name
912 return lib
914 def add_to_builtins(module_name):
915 builtins.append(get_library(module_name))
917 add_to_builtins('django.template.defaulttags')
918 add_to_builtins('django.template.defaultfilters')