Ticket #2181: __init__.py

File __init__.py, 31.4 KB (added by James Bennett, 18 years ago)

the other half

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