Ticket #7806: tplrf.diff
File tplrf.diff, 109.6 KB (added by , 16 years ago) |
---|
-
django/template/nodes.py
1 from django.utils.encoding import force_unicode 2 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping 3 from django.utils.html import escape 4 from django.template.expressions import LookupError 5 from django.conf import settings 6 7 class Node(object): 8 # Set this to True for nodes that must be first in the template (although 9 # they can be preceded by text nodes. 10 must_be_first = False 11 12 def render(self, context): 13 "Return the node rendered as a string" 14 pass 15 16 def __iter__(self): 17 yield self 18 19 def get_nodes_by_type(self, nodetype): 20 "Return a list of all nodes (within this node and its nodelist) of the given type" 21 nodes = [] 22 if isinstance(self, nodetype): 23 nodes.append(self) 24 if hasattr(self, 'nodelist'): 25 nodes.extend(self.nodelist.get_nodes_by_type(nodetype)) 26 return nodes 27 28 class NodeList(list): 29 # Set to True the first time a non-TextNode is inserted by 30 # extend_nodelist(). 31 contains_nontext = False 32 33 def render(self, context): 34 bits = [] 35 for node in self: 36 if isinstance(node, Node): 37 bits.append(self.render_node(node, context)) 38 else: 39 bits.append(node) 40 return mark_safe(''.join([force_unicode(b) for b in bits])) 41 42 def get_nodes_by_type(self, nodetype): 43 "Return a list of all nodes of the given type" 44 nodes = [] 45 for node in self: 46 nodes.extend(node.get_nodes_by_type(nodetype)) 47 return nodes 48 49 def render_node(self, node, context): 50 return node.render(context) 51 52 class TextNode(Node): 53 def __init__(self, s): 54 self.s = s 55 56 def __repr__(self): 57 return "<Text Node: '%s'>" % self.s[:25] 58 59 def render(self, context): 60 return self.s 61 62 class ExpressionNode(Node): 63 def __init__(self, expression): 64 self.expression = expression 65 66 def __repr__(self): 67 return "<Variable Node: %s>" % self.expression 68 69 def render(self, context): 70 try: 71 output = force_unicode(self.expression.resolve(context)) 72 except LookupError: 73 if settings.TEMPLATE_STRING_IF_INVALID: 74 from django.template import invalid_var_format_string 75 if invalid_var_format_string is None: 76 invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID 77 if invalid_var_format_string: 78 return settings.TEMPLATE_STRING_IF_INVALID % self.expression 79 return settings.TEMPLATE_STRING_IF_INVALID 80 else: 81 return '' 82 except UnicodeDecodeError: 83 # Unicode conversion can fail sometimes for reasons out of our 84 # control (e.g. exception rendering). In that case, we fail quietly. 85 return '' 86 if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData): 87 output = escape(output) 88 return output -
django/template/compiler.py
1 import re 2 from inspect import getargspec 3 from django.conf import settings 4 from django.template.context import Context, RequestContext, ContextPopException 5 from django.template.expressions import Expression, Literal, Lookup, FilterExpression, VARIABLE_ATTRIBUTE_SEPARATOR 6 from django.template.nodes import Node, NodeList, ExpressionNode, TextNode 7 from django.utils.text import smart_split 8 from django.utils.encoding import smart_unicode, smart_str 9 from django.utils.safestring import mark_safe 10 from django.utils.translation import ugettext 11 12 __all__ = ('Template', 'TemplateSyntaxError', 'TokenSyntaxError', 'TokenStream') 13 14 TOKEN_TEXT = 0 15 TOKEN_VAR = 1 16 TOKEN_BLOCK = 2 17 TOKEN_COMMENT = 3 18 19 # template syntax constants 20 FILTER_SEPARATOR = '|' 21 FILTER_ARGUMENT_SEPARATOR = ':' 22 BLOCK_TAG_START = '{%' 23 BLOCK_TAG_END = '%}' 24 VARIABLE_TAG_START = '{{' 25 VARIABLE_TAG_END = '}}' 26 COMMENT_TAG_START = '{#' 27 COMMENT_TAG_END = '#}' 28 SINGLE_BRACE_START = '{' 29 SINGLE_BRACE_END = '}' 30 31 ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.' 32 33 # what to report as the origin for templates that come from non-loader sources 34 # (e.g. strings) 35 UNKNOWN_SOURCE="<unknown source>" 36 37 # match a variable or block tag and capture the entire tag, including start/end delimiters 38 tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 39 re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END), 40 re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))) 41 42 class TemplateSyntaxError(Exception): 43 def __str__(self): 44 try: 45 import cStringIO as StringIO 46 except ImportError: 47 import StringIO 48 output = StringIO.StringIO() 49 output.write(Exception.__str__(self)) 50 # Check if we wrapped an exception and print that too. 51 if hasattr(self, 'exc_info'): 52 import traceback 53 output.write('\n\nOriginal ') 54 e = self.exc_info 55 traceback.print_exception(e[0], e[1], e[2], 500, output) 56 return output.getvalue() 57 58 class TemplateEncodingError(Exception): 59 pass 60 61 class Origin(object): 62 def __init__(self, name): 63 self.name = name 64 65 def reload(self): 66 raise NotImplementedError 67 68 def __str__(self): 69 return self.name 70 71 class StringOrigin(Origin): 72 def __init__(self, source): 73 super(StringOrigin, self).__init__(UNKNOWN_SOURCE) 74 self.source = source 75 76 def reload(self): 77 return self.source 78 79 class Template(object): 80 def __init__(self, template_string, origin=None, name='<Unknown Template>'): 81 try: 82 template_string = smart_unicode(template_string) 83 except UnicodeDecodeError: 84 raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.") 85 if settings.TEMPLATE_DEBUG and origin is None: 86 origin = StringOrigin(template_string) 87 self.nodelist = compile_string(template_string, origin) 88 self.name = name 89 90 def __iter__(self): 91 for node in self.nodelist: 92 for subnode in node: 93 yield subnode 94 95 def render(self, context): 96 "Display stage -- can be called many times" 97 return self.nodelist.render(context) 98 99 def compile_string(template_string, origin): 100 "Compiles template_string into NodeList ready for rendering" 101 if settings.TEMPLATE_DEBUG: 102 from debug import DebugLexer, DebugParser 103 lexer_class, parser_class = DebugLexer, DebugParser 104 else: 105 lexer_class, parser_class = Lexer, Parser 106 lexer = lexer_class(template_string, origin) 107 parser = parser_class(lexer.tokenize()) 108 return parser.parse() 109 110 class Token(object): 111 def __init__(self, token_type, contents): 112 # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT. 113 self.token_type, self.contents = token_type, contents 114 115 def __str__(self): 116 return '<%s token: "%s...">' % \ 117 ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type], 118 self.contents[:20].replace('\n', '')) 119 120 def split_contents(self): 121 return list(smart_split(self.contents)) 122 123 class Lexer(object): 124 def __init__(self, template_string, origin): 125 self.template_string = template_string 126 self.origin = origin 127 128 def tokenize(self): 129 "Return a list of tokens from a given template_string." 130 in_tag = False 131 result = [] 132 for bit in tag_re.split(self.template_string): 133 if bit: 134 result.append(self.create_token(bit, in_tag)) 135 in_tag = not in_tag 136 return result 137 138 def create_token(self, token_string, in_tag): 139 """ 140 Convert the given token string into a new Token object and return it. 141 If in_tag is True, we are processing something that matched a tag, 142 otherwise it should be treated as a literal string. 143 """ 144 if in_tag: 145 if token_string.startswith(VARIABLE_TAG_START): 146 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) 147 elif token_string.startswith(BLOCK_TAG_START): 148 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) 149 elif token_string.startswith(COMMENT_TAG_START): 150 token = Token(TOKEN_COMMENT, '') 151 else: 152 token = Token(TOKEN_TEXT, token_string) 153 return token 154 155 class Parser(object): 156 def __init__(self, tokens): 157 self.tokens = tokens 158 self.tags = {} 159 self.filters = {} 160 from django.template.library import builtins 161 for lib in builtins: 162 self.add_library(lib) 163 164 def parse(self, parse_until=None): 165 if parse_until is None: parse_until = [] 166 nodelist = self.create_nodelist() 167 while self.tokens: 168 token = self.next_token() 169 if token.token_type == TOKEN_TEXT: 170 self.extend_nodelist(nodelist, TextNode(token.contents), token) 171 elif token.token_type == TOKEN_VAR: 172 if not token.contents: 173 self.empty_variable(token) 174 filter_expression = self.compile_filter(token.contents) 175 var_node = self.create_variable_node(filter_expression) 176 self.extend_nodelist(nodelist, var_node,token) 177 elif token.token_type == TOKEN_BLOCK: 178 if token.contents in parse_until: 179 # put token back on token list so calling code knows why it terminated 180 self.prepend_token(token) 181 return nodelist 182 try: 183 command = token.contents.split()[0] 184 except IndexError: 185 self.empty_block_tag(token) 186 # execute callback function for this tag and append resulting node 187 self.enter_command(command, token) 188 try: 189 compile_func = self.tags[command] 190 except KeyError: 191 self.invalid_block_tag(token, command) 192 try: 193 compiled_result = compile_func(self, token) 194 except TemplateSyntaxError, e: 195 if not self.compile_function_error(token, e): 196 raise 197 self.extend_nodelist(nodelist, compiled_result, token) 198 self.exit_command() 199 if parse_until: 200 self.unclosed_block_tag(parse_until) 201 return nodelist 202 203 def parse_nodelist(self, parse_until=None): 204 nodelist = self.parse(parse_until=parse_until) 205 self.delete_first_token() 206 return nodelist 207 208 def skip_past(self, endtag): 209 while self.tokens: 210 token = self.next_token() 211 if token.token_type == TOKEN_BLOCK and token.contents == endtag: 212 return 213 self.unclosed_block_tag([endtag]) 214 215 def create_variable_node(self, expression): 216 return ExpressionNode(expression) 217 218 def create_nodelist(self): 219 return NodeList() 220 221 def extend_nodelist(self, nodelist, node, token): 222 if node.must_be_first and nodelist: 223 try: 224 if nodelist.contains_nontext: 225 raise AttributeError 226 except AttributeError: 227 raise TemplateSyntaxError("%r must be the first tag in the template." % node) 228 if isinstance(nodelist, NodeList) and not isinstance(node, TextNode): 229 nodelist.contains_nontext = True 230 nodelist.append(node) 231 232 def enter_command(self, command, token): 233 pass 234 235 def exit_command(self): 236 pass 237 238 def error(self, token, msg): 239 return TemplateSyntaxError(msg) 240 241 def empty_variable(self, token): 242 raise self.error(token, "Empty variable tag") 243 244 def empty_block_tag(self, token): 245 raise self.error(token, "Empty block tag") 246 247 def invalid_block_tag(self, token, command): 248 raise self.error(token, "Invalid block tag: '%s'" % command) 249 250 def unclosed_block_tag(self, parse_until): 251 raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until)) 252 253 def compile_function_error(self, token, e): 254 pass 255 256 def next_token(self): 257 return self.tokens.pop(0) 258 259 def prepend_token(self, token): 260 self.tokens.insert(0, token) 261 262 def delete_first_token(self): 263 del self.tokens[0] 264 265 def add_library(self, lib): 266 self.tags.update(lib.tags) 267 self.filters.update(lib.filters) 268 269 def token_stream(self, token): 270 return TokenStream(self, token) 271 272 def compile_filter(self, token): 273 stream = TokenStream(self, token) 274 try: 275 expr = stream.parse_expression() 276 except TokenSyntaxError: 277 stream.expected("expression") 278 stream.assert_consumed("invalid filter expression") 279 return expr 280 281 def find_filter(self, filter_name): 282 if filter_name in self.filters: 283 return self.filters[filter_name] 284 else: 285 raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name) 286 287 def filter_args_check(name, func, provided): 288 provided = list(provided) 289 plen = len(provided) 290 # Check to see if a decorator is providing the real function. 291 func = getattr(func, '_decorated_function', func) 292 args, varargs, varkw, defaults = getargspec(func) 293 294 if plen + 1 == len(args) or (defaults and plen + 1 <= len(args) + len(defaults)): 295 return True 296 297 # First argument is filter input. 298 args.pop(0) 299 if defaults: 300 nondefs = args[:-len(defaults)] 301 else: 302 nondefs = args 303 # Args without defaults must be provided. 304 try: 305 for arg in nondefs: 306 provided.pop(0) 307 except IndexError: 308 # Not enough 309 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen)) 310 311 # Defaults can be overridden. 312 defaults = defaults and list(defaults) or [] 313 try: 314 for parg in provided: 315 defaults.pop(0) 316 except IndexError: 317 # Too many. 318 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen)) 319 320 return True 321 322 323 bit_re = re.compile(r""" 324 (?P<string_literal>"(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)') 325 |(?P<numeric_literal>[+-]?\.?\d[\d\.e]*) 326 |(?P<name>[\w.]+) # keyword or variable 327 |(?P<char>\S) # punctuation 328 """, re.VERBOSE) 329 330 class TokenSyntaxError(Exception): 331 pass 332 333 def token_stream_parser(func): 334 def wrapper(self, *args, **kwargs): 335 mark = self.offset 336 try: 337 return func(self, *args, **kwargs) 338 except TokenSyntaxError: 339 self.offset = mark 340 raise 341 return wrapper 342 343 class TokenStream(object): 344 exception = TokenSyntaxError() 345 def __init__(self, parser, source): 346 self.parser = parser 347 self.source = source 348 self.offset = 0 349 self.name = None 350 if isinstance(source, Token): 351 bits = source.contents.split(None, 1) 352 self.source = len(bits) == 2 and bits[1] or '' 353 self.name = bits[0] 354 self.tokens = [(bit.lastgroup, bit.group(0)) for bit in bit_re.finditer(self.source)] 355 356 def peek(self): 357 return self.tokens[self.offset] 358 359 def consumed(self): 360 return self.offset == len(self.tokens) 361 362 def pop(self): 363 if self.offset == len(self.tokens): 364 raise self.exception 365 next = self.tokens[self.offset] 366 self.offset += 1 367 return next 368 369 def pop_lexem(self, lexem): 370 if self.offset == len(self.tokens): 371 return False 372 _, next = self.tokens[self.offset] 373 if next == lexem: 374 self.offset += 1 375 return True 376 return False 377 378 def pop_name(self): 379 if self.offset == len(self.tokens): 380 return None 381 tokentype, lexem = self.tokens[self.offset] 382 if tokentype == 'name': 383 self.offset += 1 384 return lexem 385 return None 386 387 def pushback(self): 388 self.offset -= 1 389 390 def assert_consumed(self, msg=None): 391 if self.offset != len(self.tokens): 392 raise TemplateSyntaxError, (msg or "unmatched input") + repr(self.tokens[self.offset:]) 393 394 def expected(self, what): 395 if self.consumed(): 396 found = "<EOT>" 397 else: 398 found = "<%s> %s" % self.tokens[self.offset] 399 raise TemplateSyntaxError, "expected %s, found %s" % (what, smart_str(found, encoding='ascii', errors='backslashreplace')) 400 401 @token_stream_parser 402 def parse_string(self, bare=False): 403 tokentype, lexem = self.pop() 404 if tokentype == 'string_literal': 405 return lexem.replace(r'\%s' % lexem[0], lexem[0]).replace(r'\\', '')[1:-1] 406 if bare and tokentype == 'name': 407 return lexem 408 raise self.exception 409 410 @token_stream_parser 411 def parse_value(self): 412 translate = False 413 if self.pop_lexem('_'): 414 if not self.pop_lexem('('): 415 raise self.exception 416 translate = True 417 tokentype, lexem = self.pop() 418 419 if tokentype == 'char': 420 raise self.exception 421 422 if tokentype == 'name': 423 if lexem.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or lexem[0] == '_': 424 raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % lexem) 425 value = Lookup(lexem) 426 elif tokentype == 'string_literal': 427 value = lexem.replace(r'\%s' % lexem[0], lexem[0]).replace(r'\\', '')[1:-1] 428 value = Literal(mark_safe(value)) 429 elif tokentype == 'numeric_literal': 430 try: 431 value = float(lexem) 432 except ValueError: 433 raise self.exception 434 if '.' not in lexem and 'e' not in lexem.lower(): 435 value = int(value) 436 #FIXME: this causes a test failure: `ifequal-numeric07` 437 if lexem.endswith('.'): 438 raise TemplateSyntaxError, "Numeric literals may not end with '.': %s" % lexem 439 value = Literal(value) 440 if translate: 441 if not self.pop_lexem(')'): 442 raise self.exception 443 value = FilterExpression(value, [(ugettext, ())]) 444 return value 445 446 447 @token_stream_parser 448 def parse_filter(self): 449 if not self.pop_lexem('|'): 450 raise self.exception 451 name = self.pop_name() 452 if not name: 453 raise self.exception 454 args = [] 455 if self.pop_lexem(':'): 456 args.append(self.parse_value()) 457 func = self.parser.find_filter(name) 458 filter_args_check(name, func, args) 459 return func, args 460 461 462 @token_stream_parser 463 def parse_expression(self): 464 var = self.parse_value() 465 filters = [] 466 try: 467 while True: 468 filters.append(self.parse_filter()) 469 except TokenSyntaxError: 470 pass 471 if filters: 472 return FilterExpression(var, filters) 473 return var 474 475 476 @token_stream_parser 477 def parse_expression_list(self, minimum=0, maximum=None, count=None): 478 expressions = [] 479 if count: 480 minimum = count 481 maximum = count 482 try: 483 while True: 484 if len(expressions) == maximum: 485 break 486 expressions.append(self.parse_expression()) 487 except TokenSyntaxError: 488 pass 489 if len(expressions) < minimum: 490 self.expected("expression") 491 return expressions 492 -
django/template/__init__.py
12 12 Node objects. 13 13 14 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 basic15 (TextNode), variable values in a given context (ExpressionNode), results of basic 16 16 logic (IfNode), results of looping (ForNode), or anything else. The core Node 17 types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can17 types are TextNode, ExpressionNode, IfNode and ForNode, but plugin modules can 18 18 define their own custom node types. 19 19 20 20 Each Node has a render() method, which takes a Context and returns a string of … … 48 48 >>> t.render(c) 49 49 u'<html></html>' 50 50 """ 51 import re 52 from inspect import getargspec 53 from django.conf import settings 51 54 52 from django.template.context import Context, RequestContext, ContextPopException 55 from django.utils.itercompat import is_iterable 56 from django.utils.functional import curry, Promise 57 from django.utils.text import smart_split 58 from django.utils.encoding import smart_unicode, force_unicode 59 from django.utils.translation import ugettext as _ 60 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping 61 from django.utils.html import escape 53 from django.template.nodes import Node, NodeList, TextNode, ExpressionNode 54 from django.template.compiler import Origin, StringOrigin, Template, \ 55 TemplateSyntaxError, compile_string, TokenSyntaxError 56 from django.template.library import Library, InvalidTemplateLibrary, \ 57 get_library, add_to_builtins, libraries 58 from django.template.expressions import Expression, LookupError 62 59 63 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string') 60 #backcompat 61 from django.template.compat import Variable, VariableDoesNotExist, VariableNode, \ 62 resolve_variable, TokenParser 63 from django.template.loader import TemplateDoesNotExist 64 64 65 TOKEN_TEXT = 0 66 TOKEN_VAR = 1 67 TOKEN_BLOCK = 2 68 TOKEN_COMMENT = 3 65 __all__ = ( 66 'Template', 'TemplateSyntaxError', 'compile_string', 'Origin', 'TokenSyntaxError', 67 'Context', 'RequestContext', 68 'Expression', 'LookupError', 69 #FIXME: should these be public? 'Literal', 'Lookup', 'FilterExpression' 70 'Library', 'InvalidTemplateLibrary', 'get_library', 'add_to_builtins', 'libraries', 71 #backcompat 72 'Variable', 'VariableDoesNotExist', 'resolve_variable', 'TemplateDoesNotExist', 'TokenParser', 'VariableNode', 73 ) 69 74 70 # template syntax constants71 FILTER_SEPARATOR = '|'72 FILTER_ARGUMENT_SEPARATOR = ':'73 VARIABLE_ATTRIBUTE_SEPARATOR = '.'74 BLOCK_TAG_START = '{%'75 BLOCK_TAG_END = '%}'76 VARIABLE_TAG_START = '{{'77 VARIABLE_TAG_END = '}}'78 COMMENT_TAG_START = '{#'79 COMMENT_TAG_END = '#}'80 SINGLE_BRACE_START = '{'81 SINGLE_BRACE_END = '}'82 83 ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'84 85 # what to report as the origin for templates that come from non-loader sources86 # (e.g. strings)87 UNKNOWN_SOURCE="<unknown source>"88 89 # match a variable or block tag and capture the entire tag, including start/end delimiters90 tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END),91 re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),92 re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))93 94 # global dictionary of libraries that have been loaded using get_library95 libraries = {}96 # global list of libraries to load by default for a new parser97 builtins = []98 99 75 # True if TEMPLATE_STRING_IF_INVALID contains a format string (%s). None means 100 76 # uninitialised. 101 77 invalid_var_format_string = None 102 78 103 class TemplateSyntaxError(Exception):104 def __str__(self):105 try:106 import cStringIO as StringIO107 except ImportError:108 import StringIO109 output = StringIO.StringIO()110 output.write(Exception.__str__(self))111 # Check if we wrapped an exception and print that too.112 if hasattr(self, 'exc_info'):113 import traceback114 output.write('\n\nOriginal ')115 e = self.exc_info116 traceback.print_exception(e[0], e[1], e[2], 500, output)117 return output.getvalue()118 119 class TemplateDoesNotExist(Exception):120 pass121 122 class TemplateEncodingError(Exception):123 pass124 125 class VariableDoesNotExist(Exception):126 127 def __init__(self, msg, params=()):128 self.msg = msg129 self.params = params130 131 def __str__(self):132 return unicode(self).encode('utf-8')133 134 def __unicode__(self):135 return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params])136 137 class InvalidTemplateLibrary(Exception):138 pass139 140 class Origin(object):141 def __init__(self, name):142 self.name = name143 144 def reload(self):145 raise NotImplementedError146 147 def __str__(self):148 return self.name149 150 class StringOrigin(Origin):151 def __init__(self, source):152 super(StringOrigin, self).__init__(UNKNOWN_SOURCE)153 self.source = source154 155 def reload(self):156 return self.source157 158 class Template(object):159 def __init__(self, template_string, origin=None, name='<Unknown Template>'):160 try:161 template_string = smart_unicode(template_string)162 except UnicodeDecodeError:163 raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.")164 if settings.TEMPLATE_DEBUG and origin is None:165 origin = StringOrigin(template_string)166 self.nodelist = compile_string(template_string, origin)167 self.name = name168 169 def __iter__(self):170 for node in self.nodelist:171 for subnode in node:172 yield subnode173 174 def render(self, context):175 "Display stage -- can be called many times"176 return self.nodelist.render(context)177 178 def compile_string(template_string, origin):179 "Compiles template_string into NodeList ready for rendering"180 if settings.TEMPLATE_DEBUG:181 from debug import DebugLexer, DebugParser182 lexer_class, parser_class = DebugLexer, DebugParser183 else:184 lexer_class, parser_class = Lexer, Parser185 lexer = lexer_class(template_string, origin)186 parser = parser_class(lexer.tokenize())187 return parser.parse()188 189 class Token(object):190 def __init__(self, token_type, contents):191 # token_type must be TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK or TOKEN_COMMENT.192 self.token_type, self.contents = token_type, contents193 194 def __str__(self):195 return '<%s token: "%s...">' % \196 ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],197 self.contents[:20].replace('\n', ''))198 199 def split_contents(self):200 return list(smart_split(self.contents))201 202 class Lexer(object):203 def __init__(self, template_string, origin):204 self.template_string = template_string205 self.origin = origin206 207 def tokenize(self):208 "Return a list of tokens from a given template_string."209 in_tag = False210 result = []211 for bit in tag_re.split(self.template_string):212 if bit:213 result.append(self.create_token(bit, in_tag))214 in_tag = not in_tag215 return result216 217 def create_token(self, token_string, in_tag):218 """219 Convert the given token string into a new Token object and return it.220 If in_tag is True, we are processing something that matched a tag,221 otherwise it should be treated as a literal string.222 """223 if in_tag:224 if token_string.startswith(VARIABLE_TAG_START):225 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())226 elif token_string.startswith(BLOCK_TAG_START):227 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())228 elif token_string.startswith(COMMENT_TAG_START):229 token = Token(TOKEN_COMMENT, '')230 else:231 token = Token(TOKEN_TEXT, token_string)232 return token233 234 class Parser(object):235 def __init__(self, tokens):236 self.tokens = tokens237 self.tags = {}238 self.filters = {}239 for lib in builtins:240 self.add_library(lib)241 242 def parse(self, parse_until=None):243 if parse_until is None: parse_until = []244 nodelist = self.create_nodelist()245 while self.tokens:246 token = self.next_token()247 if token.token_type == TOKEN_TEXT:248 self.extend_nodelist(nodelist, TextNode(token.contents), token)249 elif token.token_type == TOKEN_VAR:250 if not token.contents:251 self.empty_variable(token)252 filter_expression = self.compile_filter(token.contents)253 var_node = self.create_variable_node(filter_expression)254 self.extend_nodelist(nodelist, var_node,token)255 elif token.token_type == TOKEN_BLOCK:256 if token.contents in parse_until:257 # put token back on token list so calling code knows why it terminated258 self.prepend_token(token)259 return nodelist260 try:261 command = token.contents.split()[0]262 except IndexError:263 self.empty_block_tag(token)264 # execute callback function for this tag and append resulting node265 self.enter_command(command, token)266 try:267 compile_func = self.tags[command]268 except KeyError:269 self.invalid_block_tag(token, command)270 try:271 compiled_result = compile_func(self, token)272 except TemplateSyntaxError, e:273 if not self.compile_function_error(token, e):274 raise275 self.extend_nodelist(nodelist, compiled_result, token)276 self.exit_command()277 if parse_until:278 self.unclosed_block_tag(parse_until)279 return nodelist280 281 def skip_past(self, endtag):282 while self.tokens:283 token = self.next_token()284 if token.token_type == TOKEN_BLOCK and token.contents == endtag:285 return286 self.unclosed_block_tag([endtag])287 288 def create_variable_node(self, filter_expression):289 return VariableNode(filter_expression)290 291 def create_nodelist(self):292 return NodeList()293 294 def extend_nodelist(self, nodelist, node, token):295 if node.must_be_first and nodelist:296 try:297 if nodelist.contains_nontext:298 raise AttributeError299 except AttributeError:300 raise TemplateSyntaxError("%r must be the first tag in the template." % node)301 if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):302 nodelist.contains_nontext = True303 nodelist.append(node)304 305 def enter_command(self, command, token):306 pass307 308 def exit_command(self):309 pass310 311 def error(self, token, msg):312 return TemplateSyntaxError(msg)313 314 def empty_variable(self, token):315 raise self.error(token, "Empty variable tag")316 317 def empty_block_tag(self, token):318 raise self.error(token, "Empty block tag")319 320 def invalid_block_tag(self, token, command):321 raise self.error(token, "Invalid block tag: '%s'" % command)322 323 def unclosed_block_tag(self, parse_until):324 raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until))325 326 def compile_function_error(self, token, e):327 pass328 329 def next_token(self):330 return self.tokens.pop(0)331 332 def prepend_token(self, token):333 self.tokens.insert(0, token)334 335 def delete_first_token(self):336 del self.tokens[0]337 338 def add_library(self, lib):339 self.tags.update(lib.tags)340 self.filters.update(lib.filters)341 342 def compile_filter(self, token):343 "Convenient wrapper for FilterExpression"344 return FilterExpression(token, self)345 346 def find_filter(self, filter_name):347 if filter_name in self.filters:348 return self.filters[filter_name]349 else:350 raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)351 352 class TokenParser(object):353 """354 Subclass this and implement the top() method to parse a template line. When355 instantiating the parser, pass in the line from the Django template parser.356 357 The parser's "tagname" instance-variable stores the name of the tag that358 the filter was called with.359 """360 def __init__(self, subject):361 self.subject = subject362 self.pointer = 0363 self.backout = []364 self.tagname = self.tag()365 366 def top(self):367 "Overload this method to do the actual parsing and return the result."368 raise NotImplementedError()369 370 def more(self):371 "Returns True if there is more stuff in the tag."372 return self.pointer < len(self.subject)373 374 def back(self):375 "Undoes the last microparser. Use this for lookahead and backtracking."376 if not len(self.backout):377 raise TemplateSyntaxError("back called without some previous parsing")378 self.pointer = self.backout.pop()379 380 def tag(self):381 "A microparser that just returns the next tag from the line."382 subject = self.subject383 i = self.pointer384 if i >= len(subject):385 raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)386 p = i387 while i < len(subject) and subject[i] not in (' ', '\t'):388 i += 1389 s = subject[p:i]390 while i < len(subject) and subject[i] in (' ', '\t'):391 i += 1392 self.backout.append(self.pointer)393 self.pointer = i394 return s395 396 def value(self):397 "A microparser that parses for a value: some string constant or variable name."398 subject = self.subject399 i = self.pointer400 if i >= len(subject):401 raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)402 if subject[i] in ('"', "'"):403 p = i404 i += 1405 while i < len(subject) and subject[i] != subject[p]:406 i += 1407 if i >= len(subject):408 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))409 i += 1410 res = subject[p:i]411 while i < len(subject) and subject[i] in (' ', '\t'):412 i += 1413 self.backout.append(self.pointer)414 self.pointer = i415 return res416 else:417 p = i418 while i < len(subject) and subject[i] not in (' ', '\t'):419 if subject[i] in ('"', "'"):420 c = subject[i]421 i += 1422 while i < len(subject) and subject[i] != c:423 i += 1424 if i >= len(subject):425 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))426 i += 1427 s = subject[p:i]428 while i < len(subject) and subject[i] in (' ', '\t'):429 i += 1430 self.backout.append(self.pointer)431 self.pointer = i432 return s433 434 filter_raw_string = r"""435 ^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|436 ^"(?P<constant>%(str)s)"|437 ^(?P<var>[%(var_chars)s]+)|438 (?:%(filter_sep)s439 (?P<filter_name>\w+)440 (?:%(arg_sep)s441 (?:442 %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|443 "(?P<constant_arg>%(str)s)"|444 (?P<var_arg>[%(var_chars)s]+)445 )446 )?447 )""" % {448 'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",449 'var_chars': "\w\." ,450 'filter_sep': re.escape(FILTER_SEPARATOR),451 'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),452 'i18n_open' : re.escape("_("),453 'i18n_close' : re.escape(")"),454 }455 456 filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")457 filter_re = re.compile(filter_raw_string, re.UNICODE)458 459 class FilterExpression(object):460 """461 Parses a variable token and its optional filters (all as a single string),462 and return a list of tuples of the filter name and arguments.463 Sample:464 >>> token = 'variable|default:"Default value"|date:"Y-m-d"'465 >>> p = Parser('')466 >>> fe = FilterExpression(token, p)467 >>> len(fe.filters)468 2469 >>> fe.var470 <Variable: 'variable'>471 472 This class should never be instantiated outside of the473 get_filters_from_token helper function.474 """475 def __init__(self, token, parser):476 self.token = token477 matches = filter_re.finditer(token)478 var = None479 filters = []480 upto = 0481 for match in matches:482 start = match.start()483 if upto != start:484 raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s" % \485 (token[:upto], token[upto:start], token[start:]))486 if var == None:487 var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")488 if i18n_constant:489 var = '"%s"' % _(i18n_constant.replace(r'\"', '"'))490 elif constant:491 var = '"%s"' % constant.replace(r'\"', '"')492 upto = match.end()493 if var == None:494 raise TemplateSyntaxError("Could not find variable at start of %s" % token)495 elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':496 raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)497 else:498 filter_name = match.group("filter_name")499 args = []500 constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")501 if i18n_arg:502 args.append((False, _(i18n_arg.replace(r'\"', '"'))))503 elif constant_arg is not None:504 args.append((False, constant_arg.replace(r'\"', '"')))505 elif var_arg:506 args.append((True, Variable(var_arg)))507 filter_func = parser.find_filter(filter_name)508 self.args_check(filter_name,filter_func, args)509 filters.append( (filter_func,args))510 upto = match.end()511 if upto != len(token):512 raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))513 self.filters = filters514 self.var = Variable(var)515 516 def resolve(self, context, ignore_failures=False):517 try:518 obj = self.var.resolve(context)519 except VariableDoesNotExist:520 if ignore_failures:521 obj = None522 else:523 if settings.TEMPLATE_STRING_IF_INVALID:524 global invalid_var_format_string525 if invalid_var_format_string is None:526 invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID527 if invalid_var_format_string:528 return settings.TEMPLATE_STRING_IF_INVALID % self.var529 return settings.TEMPLATE_STRING_IF_INVALID530 else:531 obj = settings.TEMPLATE_STRING_IF_INVALID532 for func, args in self.filters:533 arg_vals = []534 for lookup, arg in args:535 if not lookup:536 arg_vals.append(mark_safe(arg))537 else:538 arg_vals.append(arg.resolve(context))539 if getattr(func, 'needs_autoescape', False):540 new_obj = func(obj, autoescape=context.autoescape, *arg_vals)541 else:542 new_obj = func(obj, *arg_vals)543 if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):544 obj = mark_safe(new_obj)545 elif isinstance(obj, EscapeData):546 obj = mark_for_escaping(new_obj)547 else:548 obj = new_obj549 return obj550 551 def args_check(name, func, provided):552 provided = list(provided)553 plen = len(provided)554 # Check to see if a decorator is providing the real function.555 func = getattr(func, '_decorated_function', func)556 args, varargs, varkw, defaults = getargspec(func)557 # First argument is filter input.558 args.pop(0)559 if defaults:560 nondefs = args[:-len(defaults)]561 else:562 nondefs = args563 # Args without defaults must be provided.564 try:565 for arg in nondefs:566 provided.pop(0)567 except IndexError:568 # Not enough569 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))570 571 # Defaults can be overridden.572 defaults = defaults and list(defaults) or []573 try:574 for parg in provided:575 defaults.pop(0)576 except IndexError:577 # Too many.578 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))579 580 return True581 args_check = staticmethod(args_check)582 583 def __str__(self):584 return self.token585 586 def resolve_variable(path, context):587 """588 Returns the resolved variable, which may contain attribute syntax, within589 the given context.590 591 Deprecated; use the Variable class instead.592 """593 return Variable(path).resolve(context)594 595 class Variable(object):596 """597 A template variable, resolvable against a given context. The variable may be598 a hard-coded string (if it begins and ends with single or double quote599 marks)::600 601 >>> c = {'article': {'section':u'News'}}602 >>> Variable('article.section').resolve(c)603 u'News'604 >>> Variable('article').resolve(c)605 {'section': u'News'}606 >>> class AClass: pass607 >>> c = AClass()608 >>> c.article = AClass()609 >>> c.article.section = u'News'610 >>> Variable('article.section').resolve(c)611 u'News'612 613 (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')614 """615 616 def __init__(self, var):617 self.var = var618 self.literal = None619 self.lookups = None620 self.translate = False621 622 try:623 # First try to treat this variable as a number.624 #625 # Note that this could cause an OverflowError here that we're not626 # catching. Since this should only happen at compile time, that's627 # probably OK.628 self.literal = float(var)629 630 # So it's a float... is it an int? If the original value contained a631 # dot or an "e" then it was a float, not an int.632 if '.' not in var and 'e' not in var.lower():633 self.literal = int(self.literal)634 635 # "2." is invalid636 if var.endswith('.'):637 raise ValueError638 639 except ValueError:640 # A ValueError means that the variable isn't a number.641 if var.startswith('_(') and var.endswith(')'):642 # The result of the lookup should be translated at rendering643 # time.644 self.translate = True645 var = var[2:-1]646 # If it's wrapped with quotes (single or double), then647 # we're also dealing with a literal.648 if var[0] in "\"'" and var[0] == var[-1]:649 self.literal = mark_safe(var[1:-1])650 else:651 # Otherwise we'll set self.lookups so that resolve() knows we're652 # dealing with a bonafide variable653 self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))654 655 def resolve(self, context):656 """Resolve this variable against a given context."""657 if self.lookups is not None:658 # We're dealing with a variable that needs to be resolved659 value = self._resolve_lookup(context)660 else:661 # We're dealing with a literal, so it's already been "resolved"662 value = self.literal663 if self.translate:664 return _(value)665 return value666 667 def __repr__(self):668 return "<%s: %r>" % (self.__class__.__name__, self.var)669 670 def __str__(self):671 return self.var672 673 def _resolve_lookup(self, context):674 """675 Performs resolution of a real variable (i.e. not a literal) against the676 given context.677 678 As indicated by the method's name, this method is an implementation679 detail and shouldn't be called by external code. Use Variable.resolve()680 instead.681 """682 current = context683 for bit in self.lookups:684 try: # dictionary lookup685 current = current[bit]686 except (TypeError, AttributeError, KeyError):687 try: # attribute lookup688 current = getattr(current, bit)689 if callable(current):690 if getattr(current, 'alters_data', False):691 current = settings.TEMPLATE_STRING_IF_INVALID692 else:693 try: # method call (assuming no args required)694 current = current()695 except TypeError: # arguments *were* required696 # GOTCHA: This will also catch any TypeError697 # raised in the function itself.698 current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call699 except Exception, e:700 if getattr(e, 'silent_variable_failure', False):701 current = settings.TEMPLATE_STRING_IF_INVALID702 else:703 raise704 except (TypeError, AttributeError):705 try: # list-index lookup706 current = current[int(bit)]707 except (IndexError, # list index out of range708 ValueError, # invalid literal for int()709 KeyError, # current is a dict without `int(bit)` key710 TypeError, # unsubscriptable object711 ):712 raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute713 except Exception, e:714 if getattr(e, 'silent_variable_failure', False):715 current = settings.TEMPLATE_STRING_IF_INVALID716 else:717 raise718 719 return current720 721 class Node(object):722 # Set this to True for nodes that must be first in the template (although723 # they can be preceded by text nodes.724 must_be_first = False725 726 def render(self, context):727 "Return the node rendered as a string"728 pass729 730 def __iter__(self):731 yield self732 733 def get_nodes_by_type(self, nodetype):734 "Return a list of all nodes (within this node and its nodelist) of the given type"735 nodes = []736 if isinstance(self, nodetype):737 nodes.append(self)738 if hasattr(self, 'nodelist'):739 nodes.extend(self.nodelist.get_nodes_by_type(nodetype))740 return nodes741 742 class NodeList(list):743 # Set to True the first time a non-TextNode is inserted by744 # extend_nodelist().745 contains_nontext = False746 747 def render(self, context):748 bits = []749 for node in self:750 if isinstance(node, Node):751 bits.append(self.render_node(node, context))752 else:753 bits.append(node)754 return mark_safe(''.join([force_unicode(b) for b in bits]))755 756 def get_nodes_by_type(self, nodetype):757 "Return a list of all nodes of the given type"758 nodes = []759 for node in self:760 nodes.extend(node.get_nodes_by_type(nodetype))761 return nodes762 763 def render_node(self, node, context):764 return node.render(context)765 766 class TextNode(Node):767 def __init__(self, s):768 self.s = s769 770 def __repr__(self):771 return "<Text Node: '%s'>" % self.s[:25]772 773 def render(self, context):774 return self.s775 776 class VariableNode(Node):777 def __init__(self, filter_expression):778 self.filter_expression = filter_expression779 780 def __repr__(self):781 return "<Variable Node: %s>" % self.filter_expression782 783 def render(self, context):784 try:785 output = force_unicode(self.filter_expression.resolve(context))786 except UnicodeDecodeError:787 # Unicode conversion can fail sometimes for reasons out of our788 # control (e.g. exception rendering). In that case, we fail quietly.789 return ''790 if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):791 return force_unicode(escape(output))792 else:793 return force_unicode(output)794 795 def generic_tag_compiler(params, defaults, name, node_class, parser, token):796 "Returns a template.Node subclass."797 bits = token.split_contents()[1:]798 bmax = len(params)799 def_len = defaults and len(defaults) or 0800 bmin = bmax - def_len801 if(len(bits) < bmin or len(bits) > bmax):802 if bmin == bmax:803 message = "%s takes %s arguments" % (name, bmin)804 else:805 message = "%s takes between %s and %s arguments" % (name, bmin, bmax)806 raise TemplateSyntaxError(message)807 return node_class(bits)808 809 class Library(object):810 def __init__(self):811 self.filters = {}812 self.tags = {}813 814 def tag(self, name=None, compile_function=None):815 if name == None and compile_function == None:816 # @register.tag()817 return self.tag_function818 elif name != None and compile_function == None:819 if(callable(name)):820 # @register.tag821 return self.tag_function(name)822 else:823 # @register.tag('somename') or @register.tag(name='somename')824 def dec(func):825 return self.tag(name, func)826 return dec827 elif name != None and compile_function != None:828 # register.tag('somename', somefunc)829 self.tags[name] = compile_function830 return compile_function831 else:832 raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))833 834 def tag_function(self,func):835 self.tags[getattr(func, "_decorated_function", func).__name__] = func836 return func837 838 def filter(self, name=None, filter_func=None):839 if name == None and filter_func == None:840 # @register.filter()841 return self.filter_function842 elif filter_func == None:843 if(callable(name)):844 # @register.filter845 return self.filter_function(name)846 else:847 # @register.filter('somename') or @register.filter(name='somename')848 def dec(func):849 return self.filter(name, func)850 return dec851 elif name != None and filter_func != None:852 # register.filter('somename', somefunc)853 self.filters[name] = filter_func854 return filter_func855 else:856 raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))857 858 def filter_function(self, func):859 self.filters[getattr(func, "_decorated_function", func).__name__] = func860 return func861 862 def simple_tag(self,func):863 params, xx, xxx, defaults = getargspec(func)864 865 class SimpleNode(Node):866 def __init__(self, vars_to_resolve):867 self.vars_to_resolve = map(Variable, vars_to_resolve)868 869 def render(self, context):870 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]871 return func(*resolved_vars)872 873 compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)874 compile_func.__doc__ = func.__doc__875 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)876 return func877 878 def inclusion_tag(self, file_name, context_class=Context, takes_context=False):879 def dec(func):880 params, xx, xxx, defaults = getargspec(func)881 if takes_context:882 if params[0] == 'context':883 params = params[1:]884 else:885 raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")886 887 class InclusionNode(Node):888 def __init__(self, vars_to_resolve):889 self.vars_to_resolve = map(Variable, vars_to_resolve)890 891 def render(self, context):892 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]893 if takes_context:894 args = [context] + resolved_vars895 else:896 args = resolved_vars897 898 dict = func(*args)899 900 if not getattr(self, 'nodelist', False):901 from django.template.loader import get_template, select_template902 if not isinstance(file_name, basestring) and is_iterable(file_name):903 t = select_template(file_name)904 else:905 t = get_template(file_name)906 self.nodelist = t.nodelist907 return self.nodelist.render(context_class(dict,908 autoescape=context.autoescape))909 910 compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)911 compile_func.__doc__ = func.__doc__912 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)913 return func914 return dec915 916 def get_library(module_name):917 lib = libraries.get(module_name, None)918 if not lib:919 try:920 mod = __import__(module_name, {}, {}, [''])921 except ImportError, e:922 raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))923 try:924 lib = mod.register925 libraries[module_name] = lib926 except AttributeError:927 raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)928 return lib929 930 def add_to_builtins(module_name):931 builtins.append(get_library(module_name))932 933 79 add_to_builtins('django.template.defaulttags') 934 80 add_to_builtins('django.template.defaultfilters') -
django/template/utils.py
1 import re 2 3 from django.template import Node, NodeList, TokenSyntaxError 4 5 class EmptyNode(Node): 6 def render(self, context): 7 return u'' 8 9 class ConditionalNode(Node): 10 def __init__(self, nodelist_true, nodelist_false): 11 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 12 13 def __iter__(self): 14 for node in self.nodelist_true: 15 yield node 16 for node in self.nodelist_false: 17 yield node 18 19 def get_nodes_by_type(self, nodetype): 20 nodes = [] 21 if isinstance(self, nodetype): 22 nodes.append(self) 23 nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype)) 24 nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) 25 return nodes 26 27 def check_condition(self, context): 28 return False 29 30 def render(self, context): 31 if self.check_condition(context): 32 return self.nodelist_true.render(context) 33 else: 34 return self.nodelist_false.render(context) 35 36 def parse_conditional_nodelists(parser, name): 37 end_tag = 'end' + name 38 nodelist_true = parser.parse(('else', end_tag)) 39 token = parser.next_token() 40 if token.contents == 'else': 41 nodelist_false = parser.parse((end_tag,)) 42 parser.delete_first_token() 43 else: 44 nodelist_false = NodeList() 45 return nodelist_true, nodelist_false 46 47 def parse_args_and_kwargs(self): 48 args = [] 49 kwargs = {} 50 while True: 51 name = self.pop_name() 52 if name and self.pop_lexem('='): 53 try: 54 kwargs[name] = self.parse_expression() 55 except TokenSyntaxError: 56 raise TemplateSyntaxError, "expected expression in kwargs" 57 else: 58 if name: 59 self.pushback() 60 try: 61 args.append(self.parse_expression()) 62 except TokenSyntaxError: 63 break 64 if not self.pop_lexem(','): 65 break 66 return args, kwargs 67 68 def parse_as(bits): 69 if bits.pop_lexem('as'): 70 name = bits.pop_name() 71 if name: 72 return name 73 raise bits.exception 74 75 def resolve_args_and_kwargs(args, kwargs, context): 76 resolved_args = [arg.resolve(context, True) for arg in args] 77 resolved_kwargs = {} 78 for name in kwargs: 79 resolved_kwargs[name] = kwargs[name].resolve(context, True) 80 return resolved_args, resolved_kwargs 81 -
django/template/expressions.py
1 from django.conf import settings 2 from django.utils.encoding import force_unicode 3 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping 4 5 __all__ = ('LookupError', 'Expression', 'Variable', 'Literal', 'Lookup', 'FilterExpression') 6 7 VARIABLE_ATTRIBUTE_SEPARATOR = '.' 8 9 class LookupError(Exception): 10 def __init__(self, var, msg, params=()): 11 self.var = var 12 self.msg = msg 13 self.params = params 14 15 def __str__(self): 16 return unicode(self).encode('utf-8') 17 18 def __unicode__(self): 19 return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params]) 20 21 22 class Expression(object): 23 def resolve_safe(self, context, default=None): 24 try: 25 return self.resolve(context) 26 except LookupError: 27 return default 28 29 def resolve(self, context): 30 pass 31 32 class Literal(Expression): 33 def __init__(self, value): 34 self.value = value 35 36 def resolve(self, context): 37 return self.value 38 39 class Lookup(Expression): 40 def __init__(self, var): 41 self.var = var 42 self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR)) 43 44 def __str__(self): 45 return self.var 46 47 def resolve(self, context): 48 current = context 49 for bit in self.lookups: 50 try: # dictionary lookup 51 current = current[bit] 52 except (TypeError, AttributeError, KeyError): 53 try: # attribute lookup 54 current = getattr(current, bit) 55 if callable(current): 56 if getattr(current, 'alters_data', False): 57 current = settings.TEMPLATE_STRING_IF_INVALID 58 else: 59 try: # method call (assuming no args required) 60 current = current() 61 except TypeError: # arguments *were* required 62 # GOTCHA: This will also catch any TypeError 63 # raised in the function itself. 64 current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call 65 except Exception, e: 66 if getattr(e, 'silent_variable_failure', False): 67 current = settings.TEMPLATE_STRING_IF_INVALID 68 else: 69 raise 70 except (TypeError, AttributeError): 71 try: # list-index lookup 72 current = current[int(bit)] 73 except (IndexError, # list index out of range 74 ValueError, # invalid literal for int() 75 KeyError, # current is a dict without `int(bit)` key 76 TypeError, # unsubscriptable object 77 ): 78 raise LookupError(self.var, "Failed lookup for key [%s] in %r", (bit, current)) 79 except Exception, e: 80 if getattr(e, 'silent_variable_failure', False): 81 current = settings.TEMPLATE_STRING_IF_INVALID 82 else: 83 raise 84 85 return current 86 87 class FilterExpression(Expression): 88 def __init__(self, root, filters): 89 self.root = root 90 self.filters = filters 91 92 def resolve(self, context): 93 try: 94 obj = self.root.resolve(context) 95 except LookupError: 96 if not self.filters: 97 raise 98 obj = settings.TEMPLATE_STRING_IF_INVALID 99 for func, args in self.filters: 100 arg_vals = [] 101 for arg in args: 102 arg_vals.append(arg.resolve(context)) 103 if getattr(func, 'needs_autoescape', False): 104 new_obj = func(obj, autoescape=context.autoescape, *arg_vals) 105 else: 106 new_obj = func(obj, *arg_vals) 107 if getattr(func, 'is_safe', False) and isinstance(obj, SafeData): 108 obj = mark_safe(new_obj) 109 elif isinstance(obj, EscapeData): 110 obj = mark_for_escaping(new_obj) 111 else: 112 obj = new_obj 113 return obj 114 115 def __str__(self): 116 return str(self.root)+'|<filtered>' -
django/template/defaulttags.py
9 9 from django.utils.itercompat import reversed # Python 2.3 fallback 10 10 11 11 from django.template import Node, NodeList, Template, Context, Variable 12 from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END 13 from django.template import get_library, Library, InvalidTemplateLibrary 12 from django.template import TemplateSyntaxError, LookupError, TokenSyntaxError 13 from django.template.library import get_library, Library, InvalidTemplateLibrary 14 from django.template.compiler import BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END 15 from django.template.utils import EmptyNode, ConditionalNode, parse_conditional_nodelists, parse_as, parse_args_and_kwargs 14 16 from django.conf import settings 15 from django.utils.encoding import smart_str, smart_unicode17 from django.utils.encoding import smart_str, force_unicode 16 18 from django.utils.itercompat import groupby 17 19 from django.utils.safestring import mark_safe 18 20 … … 33 35 else: 34 36 return output 35 37 36 class CommentNode(Node): 37 def render(self, context): 38 return '' 38 class CommentNode(EmptyNode): pass 39 39 40 40 class CycleNode(Node): 41 def __init__(self, cycleva rs, variable_name=None):42 self.cycle_iter = itertools_cycle( [Variable(v) for v in cyclevars])41 def __init__(self, cyclevals, variable_name=None): 42 self.cycle_iter = itertools_cycle(cyclevals) 43 43 self.variable_name = variable_name 44 44 45 45 def render(self, context): 46 value = self.cycle_iter.next().resolve (context)46 value = self.cycle_iter.next().resolve_safe(context) 47 47 if self.variable_name: 48 48 context[self.variable_name] = value 49 49 return value … … 62 62 63 63 def render(self, context): 64 64 output = self.nodelist.render(context) 65 # Apply filters.66 65 context.update({'var': output}) 67 filtered = self.filter_expr.resolve (context)66 filtered = self.filter_expr.resolve_safe(context, default='') 68 67 context.pop() 69 68 return filtered 70 69 71 70 class FirstOfNode(Node): 72 def __init__(self, va rs):73 self.va rs = map(Variable, vars)71 def __init__(self, vals): 72 self.vals = vals 74 73 75 74 def render(self, context): 76 for var in self.vars: 77 try: 78 value = var.resolve(context) 79 except VariableDoesNotExist: 80 continue 75 for val in self.vals: 76 value = val.resolve_safe(context) 81 77 if value: 82 return smart_unicode(value)78 return value 83 79 return u'' 84 80 85 81 class ForNode(Node): 86 def __init__(self, loopvars, sequence, is_reversed, nodelist _loop):82 def __init__(self, loopvars, sequence, is_reversed, nodelist): 87 83 self.loopvars, self.sequence = loopvars, sequence 88 84 self.is_reversed = is_reversed 89 self.nodelist _loop = nodelist_loop85 self.nodelist = nodelist 90 86 91 87 def __repr__(self): 92 88 reversed_text = self.is_reversed and ' reversed' or '' … … 98 94 for node in self.nodelist_loop: 99 95 yield node 100 96 101 def get_nodes_by_type(self, nodetype):102 nodes = []103 if isinstance(self, nodetype):104 nodes.append(self)105 nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))106 return nodes107 108 97 def render(self, context): 109 nodelist = NodeList()98 result = [] 110 99 if 'forloop' in context: 111 100 parentloop = context['forloop'] 112 101 else: 113 102 parentloop = {} 114 103 context.push() 115 try: 116 values = self.sequence.resolve(context, True) 117 except VariableDoesNotExist: 118 values = [] 104 values = self.sequence.resolve_safe(context, default=[]) 119 105 if values is None: 120 106 values = [] 121 107 if not hasattr(values, '__len__'): … … 144 130 context.update(dict(zip(self.loopvars, item))) 145 131 else: 146 132 context[self.loopvars[0]] = item 147 for node in self.nodelist _loop:148 nodelist.append(node.render(context))133 for node in self.nodelist: 134 result.append(node.render(context)) 149 135 if unpack: 150 136 # The loop variables were pushed on to the context so pop them 151 137 # off again. This is necessary because the tag lets the length … … 154 140 # context. 155 141 context.pop() 156 142 context.pop() 157 return nodelist.render(context)143 return mark_safe(''.join([force_unicode(b) for b in result])) 158 144 159 145 class IfChangedNode(Node): 160 def __init__(self, nodelist, *va rlist):146 def __init__(self, nodelist, *vallist): 161 147 self.nodelist = nodelist 162 148 self._last_seen = None 163 self._va rlist = map(Variable, varlist)149 self._vallist = vallist 164 150 self._id = str(id(self)) 165 151 166 152 def render(self, context): … … 168 154 self._last_seen = None 169 155 context['forloop'][self._id] = 1 170 156 try: 171 if self._va rlist:157 if self._vallist: 172 158 # Consider multiple parameters. This automatically behaves 173 159 # like an OR evaluation of the multiple variables. 174 compare_to = [var.resolve(context) for var in self._va rlist]160 compare_to = [var.resolve(context) for var in self._vallist] 175 161 else: 176 162 compare_to = self.nodelist.render(context) 177 163 except VariableDoesNotExist: … … 188 174 else: 189 175 return '' 190 176 191 class IfEqualNode( Node):192 def __init__(self, va r1, var2, nodelist_true, nodelist_false, negate):193 s elf.var1, self.var2 = Variable(var1), Variable(var2)194 self. nodelist_true, self.nodelist_false = nodelist_true, nodelist_false177 class IfEqualNode(ConditionalNode): 178 def __init__(self, val1, val2, nodelist_true, nodelist_false, negate): 179 super(IfEqualNode, self).__init__(nodelist_true, nodelist_false) 180 self.val1, self.val2 = val1, val2 195 181 self.negate = negate 196 182 197 183 def __repr__(self): 198 184 return "<IfEqualNode>" 199 185 200 def render(self, context): 201 try: 202 val1 = self.var1.resolve(context) 203 except VariableDoesNotExist: 204 val1 = None 205 try: 206 val2 = self.var2.resolve(context) 207 except VariableDoesNotExist: 208 val2 = None 209 if (self.negate and val1 != val2) or (not self.negate and val1 == val2): 210 return self.nodelist_true.render(context) 211 return self.nodelist_false.render(context) 186 def check_condition(self, context): 187 val1, val2 = self.val1.resolve_safe(context), self.val2.resolve_safe(context) 188 return self.negate == (val1 != val2) 212 189 213 class IfNode( Node):190 class IfNode(ConditionalNode): 214 191 def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type): 215 s elf.bool_exprs = bool_exprs216 self. nodelist_true, self.nodelist_false = nodelist_true, nodelist_false192 super(IfNode, self).__init__(nodelist_true, nodelist_false) 193 self.bool_exprs = bool_exprs 217 194 self.link_type = link_type 218 195 219 196 def __repr__(self): 220 197 return "<If node>" 221 198 222 def __iter__(self): 223 for node in self.nodelist_true: 224 yield node 225 for node in self.nodelist_false: 226 yield node 227 228 def get_nodes_by_type(self, nodetype): 229 nodes = [] 230 if isinstance(self, nodetype): 231 nodes.append(self) 232 nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype)) 233 nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) 234 return nodes 235 236 def render(self, context): 237 if self.link_type == IfNode.LinkTypes.or_: 238 for ifnot, bool_expr in self.bool_exprs: 239 try: 240 value = bool_expr.resolve(context, True) 241 except VariableDoesNotExist: 242 value = None 243 if (value and not ifnot) or (ifnot and not value): 244 return self.nodelist_true.render(context) 245 return self.nodelist_false.render(context) 199 def check_condition(self, context): 200 if self.link_type == 'or': 201 for negated, bool_expr in self.bool_exprs: 202 value = bool_expr.resolve_safe(context, default=False) 203 if bool(value) != negated: 204 return True 205 return False 246 206 else: 247 for ifnot, bool_expr in self.bool_exprs: 248 try: 249 value = bool_expr.resolve(context, True) 250 except VariableDoesNotExist: 251 value = None 252 if not ((value and not ifnot) or (ifnot and not value)): 253 return self.nodelist_false.render(context) 254 return self.nodelist_true.render(context) 207 for negated, bool_expr in self.bool_exprs: 208 value = bool_expr.resolve_safe(context, default=False) 209 if bool(value) == negated: 210 return False 211 return True 212 255 213 256 class LinkTypes:257 and_ = 0,258 or_ = 1259 260 214 class RegroupNode(Node): 261 215 def __init__(self, target, expression, var_name): 262 216 self.target, self.expression = target, expression 263 217 self.var_name = var_name 264 218 265 219 def render(self, context): 266 obj_list = self.target.resolve (context, True)220 obj_list = self.target.resolve_safe(context) 267 221 if obj_list == None: 268 222 # target variable wasn't found in context; fail silently. 269 223 context[self.var_name] = [] … … 273 227 context[self.var_name] = [ 274 228 {'grouper': key, 'list': list(val)} 275 229 for key, val in 276 groupby(obj_list, lambda v, f=self.expression.resolve: f(v , True))230 groupby(obj_list, lambda v, f=self.expression.resolve: f(v)) 277 231 ] 278 232 return '' 279 233 … … 310 264 return '' # Fail silently for invalid included templates. 311 265 return output 312 266 313 class LoadNode(Node): 314 def render(self, context): 315 return '' 267 class LoadNode(EmptyNode): pass 316 268 317 269 class NowNode(Node): 318 270 def __init__(self, format_string): … … 322 274 from datetime import datetime 323 275 from django.utils.dateformat import DateFormat 324 276 df = DateFormat(datetime.now()) 325 return df.format(self.format_string )277 return df.format(self.format_string.resolve_safe(context)) 326 278 327 279 class SpacelessNode(Node): 328 280 def __init__(self, nodelist): … … 357 309 358 310 def render(self, context): 359 311 from django.core.urlresolvers import reverse, NoReverseMatch 360 args = [arg.resolve (context) for arg in self.args]361 kwargs = dict([(smart_str(k,'ascii'), v.resolve (context))312 args = [arg.resolve_safe(context) for arg in self.args] 313 kwargs = dict([(smart_str(k,'ascii'), v.resolve_safe(context)) 362 314 for k, v in self.kwargs.items()]) 363 315 try: 364 316 return reverse(self.view_name, args=args, kwargs=kwargs) … … 391 343 return str(int(round(ratio))) 392 344 393 345 class WithNode(Node): 394 def __init__(self, var, name, nodelist):395 self. var = var396 self.name = name 346 def __init__(self, expr, name, nodelist): 347 self.expr = expr 348 self.name = name 397 349 self.nodelist = nodelist 398 350 399 351 def __repr__(self): 400 352 return "<WithNode>" 401 353 402 354 def render(self, context): 403 val = self.var.resolve(context)404 355 context.push() 405 context[self.name] = val356 context[self.name] = self.expr.resolve_safe(context) 406 357 output = self.nodelist.render(context) 407 358 context.pop() 408 359 return output … … 418 369 arg = args[1] 419 370 if arg not in (u'on', u'off'): 420 371 raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'") 421 nodelist = parser.parse(('endautoescape',)) 422 parser.delete_first_token() 372 nodelist = parser.parse_nodelist(('endautoescape',)) 423 373 return AutoEscapeControlNode((arg == 'on'), nodelist) 424 374 autoescape = register.tag(autoescape) 425 375 … … 489 439 490 440 if len(args) > 4 and args[-2] == 'as': 491 441 name = args[-1] 492 node = CycleNode(args[1:-2], name) 442 values = [parser.compile_filter(arg) for arg in args[1:-2]] 443 node = CycleNode(values, name) 493 444 if not hasattr(parser, '_namedCycleNodes'): 494 445 parser._namedCycleNodes = {} 495 446 parser._namedCycleNodes[name] = node 496 447 else: 497 node = CycleNode(args[1:]) 448 values = [parser.compile_filter(arg) for arg in args[1:]] 449 node = CycleNode(values) 498 450 return node 499 451 cycle = register.tag(cycle) 500 452 … … 527 479 {% endfilter %} 528 480 """ 529 481 _, rest = token.contents.split(None, 1) 530 filter_expr = parser.compile_filter("var|%s" % (rest))482 filter_expr = parser.compile_filter("var|%s" % rest) 531 483 for func, unused in filter_expr.filters: 532 484 if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'): 533 485 raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) 534 nodelist = parser.parse(('endfilter',)) 535 parser.delete_first_token() 486 nodelist = parser.parse_nodelist(('endfilter',)) 536 487 return FilterNode(filter_expr, nodelist) 537 488 do_filter = register.tag("filter", do_filter) 538 489 … … 565 516 {% firstof var1 var2 var3 "fallback value" %} 566 517 567 518 """ 568 bits = token.split_contents()[1:] 569 if len(bits) < 1: 570 raise TemplateSyntaxError("'firstof' statement requires at least one" 571 " argument") 572 return FirstOfNode(bits) 519 bits = parser.token_stream(token) 520 expressions = bits.parse_expression_list(minimum=1) 521 bits.assert_consumed() 522 return FirstOfNode(expressions) 573 523 firstof = register.tag(firstof) 574 524 575 525 #@register.tag(name="for") … … 612 562 ========================== ================================================ 613 563 614 564 """ 615 bits = token.contents.split() 616 if len(bits) < 4: 617 raise TemplateSyntaxError("'for' statements should have at least four" 618 " words: %s" % token.contents) 619 620 is_reversed = bits[-1] == 'reversed' 621 in_index = is_reversed and -3 or -2 622 if bits[in_index] != 'in': 623 raise TemplateSyntaxError("'for' statements should use the format" 624 " 'for x in y': %s" % token.contents) 625 626 loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',') 627 for var in loopvars: 628 if not var or ' ' in var: 629 raise TemplateSyntaxError("'for' tag received an invalid argument:" 630 " %s" % token.contents) 631 632 sequence = parser.compile_filter(bits[in_index+1]) 633 nodelist_loop = parser.parse(('endfor',)) 634 parser.delete_first_token() 635 return ForNode(loopvars, sequence, is_reversed, nodelist_loop) 565 bits = parser.token_stream(token) 566 loopvars = [] 567 while True: 568 var = bits.pop_name() 569 if var: 570 loopvars.append(var) 571 if not bits.pop_lexem(','): 572 break 573 else: 574 break 575 if not loopvars: 576 raise TemplateSyntaxError("'for' tag requires at least one loopvar") 577 578 if not bits.pop_lexem('in'): 579 raise TemplateSyntaxError("'for' tag requires 'in' keyword") 580 try: 581 sequence = bits.parse_expression() 582 except TokenSyntaxError: 583 raise bits.expected("expression") 584 reversed = bits.pop_lexem('reversed') 585 bits.assert_consumed() 586 nodelist = parser.parse_nodelist(('endfor',)) 587 return ForNode(loopvars, sequence, reversed, nodelist) 636 588 do_for = register.tag("for", do_for) 637 589 638 590 def do_ifequal(parser, token, negate): 639 bits = list(token.split_contents()) 640 if len(bits) != 3: 641 raise TemplateSyntaxError, "%r takes two arguments" % bits[0] 642 end_tag = 'end' + bits[0] 643 nodelist_true = parser.parse(('else', end_tag)) 644 token = parser.next_token() 645 if token.contents == 'else': 646 nodelist_false = parser.parse((end_tag,)) 647 parser.delete_first_token() 648 else: 649 nodelist_false = NodeList() 650 return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate) 591 bits = parser.token_stream(token) 592 val1, val2 = bits.parse_expression_list(count=2) 593 bits.assert_consumed() 594 nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name) 595 return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate) 651 596 652 597 #@register.tag 653 598 def ifequal(parser, token): … … 737 682 {% endif %} 738 683 {% endif %} 739 684 """ 740 bits = token.contents.split() 741 del bits[0] 742 if not bits: 743 raise TemplateSyntaxError("'if' statement requires at least one argument") 744 # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d'] 745 bitstr = ' '.join(bits) 746 boolpairs = bitstr.split(' and ') 685 bits = parser.token_stream(token) 686 link_type = None 687 link = None 747 688 boolvars = [] 748 if len(boolpairs) == 1: 749 link_type = IfNode.LinkTypes.or_ 750 boolpairs = bitstr.split(' or ') 751 else: 752 link_type = IfNode.LinkTypes.and_ 753 if ' or ' in bitstr: 754 raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'" 755 for boolpair in boolpairs: 756 if ' ' in boolpair: 757 try: 758 not_, boolvar = boolpair.split() 759 except ValueError: 760 raise TemplateSyntaxError, "'if' statement improperly formatted" 761 if not_ != 'not': 762 raise TemplateSyntaxError, "Expected 'not' in if statement" 763 boolvars.append((True, parser.compile_filter(boolvar))) 689 while True: 690 negated = False 691 if bits.pop_lexem('not'): 692 negated = True 693 try: 694 expr = bits.parse_expression() 695 except TokenSyntaxError: 696 if link: 697 raise TemplateSyntaxError("'if' statement improperly formatted") 698 else: 699 raise TemplateSyntaxError("'if' statement requires at least one argument") 700 boolvars.append((negated, expr)) 701 link = bits.pop_name() 702 if not link: 703 break 704 if link_type: 705 if link_type != link: 706 raise TemplateSyntaxError("'if' tags can't mix 'and' and 'or'") 764 707 else: 765 boolvars.append((False, parser.compile_filter(boolpair))) 766 nodelist_true = parser.parse(('else', 'endif')) 767 token = parser.next_token() 768 if token.contents == 'else': 769 nodelist_false = parser.parse(('endif',)) 770 parser.delete_first_token() 771 else: 772 nodelist_false = NodeList() 708 if not link in ('and', 'or'): 709 raise TemplateSyntaxError("'if' tag expects 'and' or 'or', got: %s" % link) 710 link_type = link 711 bits.assert_consumed() 712 713 nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'if') 714 773 715 return IfNode(boolvars, nodelist_true, nodelist_false, link_type) 774 716 do_if = register.tag("if", do_if) 775 717 … … 802 744 {% endifchanged %} 803 745 {% endfor %} 804 746 """ 805 bits = token.contents.split() 806 nodelist = parser.parse(('endifchanged',)) 807 parser.delete_first_token() 808 return IfChangedNode(nodelist, *bits[1:]) 747 bits = parser.token_stream(token) 748 nodelist = parser.parse_nodelist(('endifchanged',)) 749 values = bits.parse_expression_list() 750 bits.assert_consumed() 751 return IfChangedNode(nodelist, *values) 809 752 ifchanged = register.tag(ifchanged) 810 753 811 754 #@register.tag … … 872 815 873 816 It is {% now "jS F Y H:i" %} 874 817 """ 875 bits = token.contents.split('"') 876 if len(bits) != 3: 877 raise TemplateSyntaxError, "'now' statement takes one argument" 878 format_string = bits[1] 818 bits = parser.token_stream(token) 819 try: 820 format_string = bits.parse_expression() 821 except TokenSyntaxError: 822 bits.expected("expression") 823 bits.assert_consumed() 879 824 return NowNode(format_string) 880 825 now = register.tag(now) 881 826 … … 926 871 {% regroup people|dictsort:"gender" by gender as grouped %} 927 872 928 873 """ 929 firstbits = token.contents.split(None, 3) 930 if len(firstbits) != 4: 931 raise TemplateSyntaxError, "'regroup' tag takes five arguments" 932 target = parser.compile_filter(firstbits[1]) 933 if firstbits[2] != 'by': 934 raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") 935 lastbits_reversed = firstbits[3][::-1].split(None, 2) 936 if lastbits_reversed[1][::-1] != 'as': 937 raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" 938 " be 'as'") 939 940 expression = parser.compile_filter(lastbits_reversed[2][::-1]) 941 942 var_name = lastbits_reversed[0][::-1] 874 bits = parser.token_stream(token) 875 try: 876 target = bits.parse_expression() 877 except TokenSyntaxError: 878 bits.expected("expression") 879 880 if not bits.pop_lexem('by'): 881 raise bits.expected("'by'") 882 try: 883 expression = bits.parse_expression() 884 except TokenSyntaxError: 885 raise TemplateSyntaxError() 886 try: 887 var_name = parse_as(bits) 888 except TokenSyntaxError: 889 raise bits.expected("as <name>") 890 bits.assert_consumed() 943 891 return RegroupNode(target, expression, var_name) 944 892 regroup = register.tag(regroup) 945 893 … … 968 916 </strong> 969 917 {% endspaceless %} 970 918 """ 971 nodelist = parser.parse(('endspaceless',)) 972 parser.delete_first_token() 973 return SpacelessNode(nodelist) 919 return SpacelessNode(parser.parse_nodelist(('endspaceless',))) 974 920 spaceless = register.tag(spaceless) 975 921 976 922 #@register.tag … … 1038 984 1039 985 The URL will look like ``/clients/client/123/``. 1040 986 """ 1041 bits = token.contents.split(' ', 2) 1042 if len(bits) < 2: 1043 raise TemplateSyntaxError("'%s' takes at least one argument" 1044 " (path to a view)" % bits[0]) 1045 args = [] 1046 kwargs = {} 1047 if len(bits) > 2: 1048 for arg in bits[2].split(','): 1049 if '=' in arg: 1050 k, v = arg.split('=', 1) 1051 k = k.strip() 1052 kwargs[k] = parser.compile_filter(v) 1053 else: 1054 args.append(parser.compile_filter(arg)) 1055 return URLNode(bits[1], args, kwargs) 987 bits = parser.token_stream(token) 988 try: 989 view = bits.parse_string(bare=True) 990 except TokenSyntaxError: 991 raise bits.expected("viewname") 992 args, kwargs = parse_args_and_kwargs(bits) 993 bits.assert_consumed() 994 return URLNode(view, args, kwargs) 1056 995 url = register.tag(url) 1057 996 1058 997 #@register.tag … … 1084 1023 #@register.tag 1085 1024 def do_with(parser, token): 1086 1025 """ 1087 Adds a valueto the context (inside of this block) for caching and easy1026 Adds values to the context (inside of this block) for caching and easy 1088 1027 access. 1089 1028 1090 1029 For example:: … … 1092 1031 {% with person.some_sql_method as total %} 1093 1032 {{ total }} object{{ total|pluralize }} 1094 1033 {% endwith %} 1034 1035 {% with person.some_sql_method as total, person.get_full_name as full_name %} 1036 {{ full_name }}: {{ total }} object{{ total|pluralize }} 1037 {% endwith %} 1038 1095 1039 """ 1096 bits = list(token.split_contents())1097 if len(bits) != 4 or bits[2] != "as":1098 raise TemplateSyntaxError("%r expected format is 'value as name'" %1099 bits[0])1100 var = parser.compile_filter(bits[1])1101 name = bits[3]1102 nodelist = parser.parse(('endwith',))1103 parser.delete_first_token()1104 return WithNode( var, name, nodelist)1040 bits = parser.token_stream(token) 1041 try: 1042 expr = bits.parse_expression() 1043 name = parse_as(bits) 1044 except TokenSyntaxError: 1045 raise TemplateSyntaxError("%r expected format is 'value as name'" % bits.name) 1046 bits.assert_consumed() 1047 nodelist = parser.parse_nodelist(('endwith',)) 1048 return WithNode(expr, name, nodelist) 1105 1049 do_with = register.tag('with', do_with) -
django/template/compat.py
1 import warnings 2 from django.template.expressions import Expression, LookupError 3 from django.template.compiler import TemplateSyntaxError 4 from django.template.nodes import ExpressionNode 5 6 VariableDoesNotExist = LookupError 7 VariableNode = ExpressionNode 8 9 class Variable(Expression): 10 def __init__(self, var): 11 warnings.warn('Use Lookup instead of Variable.', DeprecationWarning, stacklevel=2) 12 self.var = var 13 from django.template.compiler import TokenStream 14 stream = TokenStream(None, var) 15 self.expression = stream.parse_value() 16 stream.assert_consumed("Invalid variable: %s" % var) 17 18 def resolve(self, context): 19 return self.expression.resolve(context) 20 21 def __repr__(self): 22 return "<%s: %r>" % (self.__class__.__name__, self.var) 23 24 def __str__(self): 25 return self.var 26 27 28 def resolve_variable(path, context): 29 """ 30 Returns the resolved variable, which may contain attribute syntax, within 31 the given context. 32 33 Deprecated. 34 """ 35 warnings.warn('Use Lookup instead of resolve_variable.', DeprecationWarning, stacklevel=2) 36 from django.template.compiler import TokenStream 37 stream = TokenStream(None, path) 38 val = stream.parse_value() 39 stream.assert_consumed("Invalid variable: %s" % path) 40 return val.resolve(context) 41 42 43 class TokenParser(object): 44 """ 45 Subclass this and implement the top() method to parse a template line. When 46 instantiating the parser, pass in the line from the Django template parser. 47 48 The parser's "tagname" instance-variable stores the name of the tag that 49 the filter was called with. 50 """ 51 def __init__(self, subject): 52 self.subject = subject 53 self.pointer = 0 54 self.backout = [] 55 self.tagname = self.tag() 56 57 def top(self): 58 "Overload this method to do the actual parsing and return the result." 59 raise NotImplementedError() 60 61 def more(self): 62 "Returns True if there is more stuff in the tag." 63 return self.pointer < len(self.subject) 64 65 def back(self): 66 "Undoes the last microparser. Use this for lookahead and backtracking." 67 if not len(self.backout): 68 raise TemplateSyntaxError("back called without some previous parsing") 69 self.pointer = self.backout.pop() 70 71 def tag(self): 72 "A microparser that just returns the next tag from the line." 73 subject = self.subject 74 i = self.pointer 75 if i >= len(subject): 76 raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject) 77 p = i 78 while i < len(subject) and subject[i] not in (' ', '\t'): 79 i += 1 80 s = subject[p:i] 81 while i < len(subject) and subject[i] in (' ', '\t'): 82 i += 1 83 self.backout.append(self.pointer) 84 self.pointer = i 85 return s 86 87 def value(self): 88 "A microparser that parses for a value: some string constant or variable name." 89 subject = self.subject 90 i = self.pointer 91 if i >= len(subject): 92 raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject) 93 if subject[i] in ('"', "'"): 94 p = i 95 i += 1 96 while i < len(subject) and subject[i] != subject[p]: 97 i += 1 98 if i >= len(subject): 99 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject)) 100 i += 1 101 res = subject[p:i] 102 while i < len(subject) and subject[i] in (' ', '\t'): 103 i += 1 104 self.backout.append(self.pointer) 105 self.pointer = i 106 return res 107 else: 108 p = i 109 while i < len(subject) and subject[i] not in (' ', '\t'): 110 if subject[i] in ('"', "'"): 111 c = subject[i] 112 i += 1 113 while i < len(subject) and subject[i] != c: 114 i += 1 115 if i >= len(subject): 116 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject)) 117 i += 1 118 s = subject[p:i] 119 while i < len(subject) and subject[i] in (' ', '\t'): 120 i += 1 121 self.backout.append(self.pointer) 122 self.pointer = i 123 return s -
django/template/loader_tags.py
1 from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable2 from django.template import Library, Node, TextNode3 from django.template. loader import get_template, get_template_from_string, find_template_source1 from django.template import TemplateSyntaxError, Variable, Library, Node, TextNode 2 from django.template.loader import TemplateDoesNotExist, get_template, get_template_from_string, find_template_source 3 from django.template.expressions import Literal 4 4 from django.conf import settings 5 5 from django.utils.safestring import mark_safe 6 6 … … 39 39 class ExtendsNode(Node): 40 40 must_be_first = True 41 41 42 def __init__(self, nodelist, parent_name, parent_name_expr,template_dirs=None):42 def __init__(self, nodelist, parent_name, template_dirs=None): 43 43 self.nodelist = nodelist 44 self.parent_name , self.parent_name_expr = parent_name, parent_name_expr44 self.parent_name = parent_name 45 45 self.template_dirs = template_dirs 46 46 47 47 def __repr__(self): 48 if self.parent_name_expr:49 return "<ExtendsNode: extends %s>" % self.parent_name_expr.token50 48 return '<ExtendsNode: extends "%s">' % self.parent_name 51 49 52 50 def get_parent(self, context): 53 if self.parent_name_expr: 54 self.parent_name = self.parent_name_expr.resolve(context) 55 parent = self.parent_name 51 parent = self.parent_name.resolve_safe(context) 56 52 if not parent: 57 53 error_msg = "Invalid template name in 'extends' tag: %r." % parent 58 if self.parent_name_expr:59 error_msg += " Got this from the '%s' variable." % self.parent_name_expr.token60 54 raise TemplateSyntaxError, error_msg 61 55 if hasattr(parent, 'render'): 62 56 return parent # parent is a Template object … … 114 108 115 109 class IncludeNode(Node): 116 110 def __init__(self, template_name): 117 self.template_name = Variable(template_name)111 self.template_name = template_name 118 112 119 113 def render(self, context): 120 114 try: 121 template_name = self.template_name.resolve (context)115 template_name = self.template_name.resolve_safe(context) 122 116 t = get_template(template_name) 123 117 return t.render(context) 124 118 except TemplateSyntaxError, e: … … 161 155 bits = token.contents.split() 162 156 if len(bits) != 2: 163 157 raise TemplateSyntaxError, "'%s' takes one argument" % bits[0] 164 parent_name, parent_name_expr = None, None 165 if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]: 166 parent_name = bits[1][1:-1] 167 else: 168 parent_name_expr = parser.compile_filter(bits[1]) 158 parent_name = parser.compile_filter(bits[1]) 169 159 nodelist = parser.parse() 170 160 if nodelist.get_nodes_by_type(ExtendsNode): 171 161 raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0] 172 return ExtendsNode(nodelist, parent_name , parent_name_expr)162 return ExtendsNode(nodelist, parent_name) 173 163 174 164 def do_include(parser, token): 175 165 """ … … 179 169 180 170 {% include "foo/some_include" %} 181 171 """ 182 bits = token.contents.split() 183 if len(bits) != 2: 184 raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0] 185 path = bits[1] 186 if path[0] in ('"', "'") and path[-1] == path[0]: 187 return ConstantIncludeNode(path[1:-1]) 188 return IncludeNode(bits[1]) 172 bits = parser.token_stream(token) 173 try: 174 template_name = bits.parse_expression() 175 except TokenSyntaxError: 176 raise bits.expected("expression") 177 if isinstance(template_name, Literal): 178 # remove ConstantIncludeNode and this hack will be gone 179 return ConstantIncludeNode(template_name.resolve(None)) 180 return IncludeNode(template_name) 189 181 190 182 register.tag('block', do_block) 191 183 register.tag('extends', do_extends) -
django/template/library.py
1 import re 2 from inspect import getargspec 3 from django.conf import settings 4 from django.template.context import Context 5 from django.template.nodes import Node 6 from django.template.compiler import TemplateSyntaxError 7 from django.utils.itercompat import is_iterable 8 from django.utils.functional import curry 9 10 __all__ = ('InvalidTemplateLibrary', 'Library', 'get_library', 'add_to_builtins') 11 12 # global dictionary of libraries that have been loaded using get_library 13 libraries = {} 14 # global list of libraries to load by default for a new parser 15 builtins = [] 16 17 class InvalidTemplateLibrary(Exception): 18 pass 19 20 def generic_tag_compiler(params, defaults, name, node_class, parser, token, takes_context=False, takes_nodelist=False): 21 "Returns a template.Node subclass." 22 bits = parser.token_stream(token) 23 bmax = len(params) 24 def_len = defaults and len(defaults) or 0 25 bmin = bmax - def_len 26 args = stream.parse_expression_list(minimum=bmin, maximum=bmax) 27 stream.assert_comsumed() 28 if takes_context: 29 node_class = curry(node_class, takes_context=takes_context) 30 if takes_nodelist: 31 nodelist = parser.parse_nodelist(('end%s' % name,)) 32 node_class = curry(node_class, nodelist=nodelist) 33 return node_class(args) 34 35 class Library(object): 36 def __init__(self): 37 self.filters = {} 38 self.tags = {} 39 40 def tag(self, name=None, compile_function=None): 41 if name == None and compile_function == None: 42 # @register.tag() 43 return self.tag_function 44 elif name != None and compile_function == None: 45 if(callable(name)): 46 # @register.tag 47 return self.tag_function(name) 48 else: 49 # @register.tag('somename') or @register.tag(name='somename') 50 def dec(func): 51 return self.tag(name, func) 52 return dec 53 elif name != None and compile_function != None: 54 # register.tag('somename', somefunc) 55 self.tags[name] = compile_function 56 return compile_function 57 else: 58 raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)) 59 60 def tag_function(self,func): 61 self.tags[getattr(func, "_decorated_function", func).__name__] = func 62 return func 63 64 def filter(self, name=None, filter_func=None): 65 if name == None and filter_func == None: 66 # @register.filter() 67 return self.filter_function 68 elif filter_func == None: 69 if(callable(name)): 70 # @register.filter 71 return self.filter_function(name) 72 else: 73 # @register.filter('somename') or @register.filter(name='somename') 74 def dec(func): 75 return self.filter(name, func) 76 return dec 77 elif name != None and filter_func != None: 78 # register.filter('somename', somefunc) 79 self.filters[name] = filter_func 80 return filter_func 81 else: 82 raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func)) 83 84 def filter_function(self, func): 85 self.filters[getattr(func, "_decorated_function", func).__name__] = func 86 return func 87 88 def simple_tag(self, compile_function=None): 89 def dec(func): 90 params, xx, xxx, defaults = getargspec(func) 91 92 class SimpleNode(Node): 93 def __init__(self, args): 94 self.args = args 95 96 def render(self, context): 97 return func(*[arg.resolve_safe(context) for arg in self.args]) 98 99 compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode) 100 compile_func.__doc__ = func.__doc__ 101 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func) 102 return func 103 104 if callable(compile_function): 105 # @register.simple_tag 106 return dec(compile_function) 107 return dec 108 109 def inclusion_tag(self, file_name, context_class=Context, takes_context=False): 110 def dec(func): 111 params, xx, xxx, defaults = getargspec(func) 112 if takes_context: 113 if params[0] == 'context': 114 params = params[1:] 115 else: 116 raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'") 117 118 class InclusionNode(Node): 119 def __init__(self, args): 120 self.args = args 121 122 def render(self, context): 123 resolved_vars = [arg.resolve_safe(context) for arg in self.args] 124 if takes_context: 125 args = [context] + resolved_vars 126 else: 127 args = resolved_vars 128 129 dict = func(*args) 130 131 if not getattr(self, 'nodelist', False): 132 from django.template.loader import get_template, select_template 133 if not isinstance(file_name, basestring) and is_iterable(file_name): 134 t = select_template(file_name) 135 else: 136 t = get_template(file_name) 137 self.nodelist = t.nodelist 138 return self.nodelist.render(context_class(dict, autoescape=context.autoescape)) 139 140 compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode) 141 compile_func.__doc__ = func.__doc__ 142 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func) 143 return func 144 return dec 145 146 def get_library(module_name): 147 lib = libraries.get(module_name, None) 148 if not lib: 149 try: 150 mod = __import__(module_name, {}, {}, ['']) 151 except ImportError, e: 152 raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e)) 153 try: 154 lib = mod.register 155 libraries[module_name] = lib 156 except AttributeError: 157 raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name) 158 return lib 159 160 def add_to_builtins(module_name): 161 builtins.append(get_library(module_name)) -
django/template/debug.py
1 from django.template import Lexer, Parser, tag_re, NodeList, VariableNode, TemplateSyntaxError 1 from django.template.compiler import Lexer, Parser, tag_re 2 from django.template import NodeList, ExpressionNode, TemplateSyntaxError 2 3 from django.utils.encoding import force_unicode 3 4 from django.utils.html import escape 4 5 from django.utils.safestring import SafeData, EscapeData … … 50 51 return DebugNodeList() 51 52 52 53 def create_variable_node(self, contents): 53 return Debug VariableNode(contents)54 return DebugExpressionNode(contents) 54 55 55 56 def extend_nodelist(self, nodelist, node, token): 56 57 node.source = token.source … … 81 82 raise wrapped 82 83 return result 83 84 84 class Debug VariableNode(VariableNode):85 class DebugExpressionNode(ExpressionNode): 85 86 def render(self, context): 86 87 try: 87 output = force_unicode(self.filter_expression.resolve(context))88 return super(DebugExpressionNode, self).render(context) 88 89 except TemplateSyntaxError, e: 89 90 if not hasattr(e, 'source'): 90 91 e.source = self.source 91 92 raise 92 except UnicodeDecodeError:93 return ''94 if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):95 return escape(output)96 else:97 return output -
django/template/loader.py
21 21 # installed, because pkg_resources is necessary to read eggs. 22 22 23 23 from django.core.exceptions import ImproperlyConfigured 24 from django.template import Origin, Template, Context, TemplateDoesNotExist,add_to_builtins24 from django.template import Origin, Template, Context, add_to_builtins 25 25 from django.conf import settings 26 26 27 27 template_source_loaders = None 28 28 29 class TemplateDoesNotExist(Exception): 30 pass 31 29 32 class LoaderOrigin(Origin): 30 33 def __init__(self, display_name, loader, name, dirs): 31 34 super(LoaderOrigin, self).__init__(display_name) -
django/templatetags/i18n.py
1 1 import re 2 2 3 from django.template import Node, Variable, VariableNode 4 from django.template import TemplateSyntaxError, TokenParser, Library 5 from django.template import TOKEN_TEXT, TOKEN_VAR 3 from django.template import Node, VariableNode, TokenParser, Variable, TemplateSyntaxError, Library 4 from django.template.compiler import TOKEN_TEXT, TOKEN_VAR 6 5 from django.utils import translation 7 6 from django.utils.encoding import force_unicode 8 7 … … 35 34 36 35 class TranslateNode(Node): 37 36 def __init__(self, value, noop): 38 self.value = Variable(value)37 self.value = value 39 38 self.noop = noop 40 39 41 40 def render(self, context): … … 171 170 the variable ``variable``. Make sure that the string 172 171 in there is something that is in the .po file. 173 172 """ 174 class TranslateParser(TokenParser): 175 def top(self): 176 value = self.value() 177 if self.more(): 178 if self.tag() == 'noop': 179 noop = True 180 else: 181 raise TemplateSyntaxError, "only option for 'trans' is 'noop'" 182 else: 183 noop = False 184 return (value, noop) 185 value, noop = TranslateParser(token.contents).top() 173 bits = parser.token_stream(token) 174 try: 175 value = bits.parse_expression() 176 except TokenSyntaxError: 177 bits.expected("expression") 178 noop = bits.pop_lexem('noop') 179 bits.assert_consumed() 186 180 return TranslateNode(value, noop) 187 181 188 182 def do_block_translate(parser, token): -
django/templatetags/cache.py
1 from django.template import Library, Node, TemplateSyntaxError, Variable, VariableDoesNotExist 2 from django.template import resolve_variable 1 from django.template import Library, Node, TemplateSyntaxError, VariableDoesNotExist, TokenSyntaxError 3 2 from django.core.cache import cache 4 3 from django.utils.encoding import force_unicode 5 4 … … 8 7 class CacheNode(Node): 9 8 def __init__(self, nodelist, expire_time_var, fragment_name, vary_on): 10 9 self.nodelist = nodelist 11 self.expire_time_var = Variable(expire_time_var)10 self.expire_time_var = expire_time_var 12 11 self.fragment_name = fragment_name 13 12 self.vary_on = vary_on 14 13 … … 22 21 except (ValueError, TypeError): 23 22 raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time) 24 23 # Build a unicode key for this fragment and all vary-on's. 25 cache_key = u':'.join([self.fragment_name] + [force_unicode( resolve_variable(var,context)) for var in self.vary_on])24 cache_key = u':'.join([self.fragment_name] + [force_unicode(var.resolve_safe(context)) for var in self.vary_on]) 26 25 value = cache.get(cache_key) 27 26 if value is None: 28 27 value = self.nodelist.render(context) … … 50 49 51 50 Each unique set of arguments will result in a unique cache entry. 52 51 """ 53 nodelist = parser.parse(('endcache',)) 54 parser.delete_first_token() 55 tokens = token.contents.split() 56 if len(tokens) < 3: 57 raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0]) 58 return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:]) 52 nodelist = parser.parse_nodelist(('endcache',)) 53 bits = parser.token_stream(token) 54 try: 55 expire_time = bits.parse_expression() 56 except TokenSyntaxError: 57 bits.expected("expression") 58 name = bits.pop_name() 59 if not name: 60 raise TemplateSyntaxError, "'cache' requires a fragment name" 61 vary_on = bits.parse_expression_list() 62 bits.assert_consumed() 63 return CacheNode(nodelist, expire_time, name, vary_on) 59 64 60 65 register.tag('cache', do_cache)