Ticket #7806: tplrf-r8970.diff
File tplrf-r8970.diff, 129.2 KB (added by , 16 years ago) |
---|
-
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 uses_token_stream, 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): … … 141 140 raise TemplateSyntaxError, "'get_current_language_bidi' requires 'as variable' (got %r)" % args 142 141 return GetCurrentLanguageBidiNode(args[2]) 143 142 144 def do_translate(parser, token): 143 144 #@uses_token_stream 145 def do_translate(parser, bits): 145 146 """ 146 147 This will mark a string for translation and will 147 148 translate the string for the current language. … … 171 172 the variable ``variable``. Make sure that the string 172 173 in there is something that is in the .po file. 173 174 """ 174 class TranslateParser(TokenParser): 175 def top(self): 176 value = self.value() 177 if self.more(): 178 if self.tag() == 'noop': 179 noop = True 180 else: 181 raise TemplateSyntaxError, "only option for 'trans' is 'noop'" 182 else: 183 noop = False 184 return (value, noop) 185 value, noop = TranslateParser(token.contents).top() 175 value = bits.parse_expression(required=True) 176 noop = bits.pop_lexem('noop') 186 177 return TranslateNode(value, noop) 187 178 188 179 def do_block_translate(parser, token): … … 255 246 register.tag('get_available_languages', do_get_available_languages) 256 247 register.tag('get_current_language', do_get_current_language) 257 248 register.tag('get_current_language_bidi', do_get_current_language_bidi) 258 register.tag('trans', do_translate)249 register.tag('trans', uses_token_stream(do_translate)) 259 250 register.tag('blocktrans', do_block_translate) -
django/templatetags/cache.py
1 from django.template import Library, Node, TemplateSyntaxError, Variable , VariableDoesNotExist2 from django.template import resolve_variable1 from django.template import Library, Node, TemplateSyntaxError, VariableDoesNotExist, TokenSyntaxError 2 from django.template.compiler import uses_token_stream 3 3 from django.core.cache import cache 4 4 from django.utils.encoding import force_unicode 5 5 from django.utils.http import urlquote … … 9 9 class CacheNode(Node): 10 10 def __init__(self, nodelist, expire_time_var, fragment_name, vary_on): 11 11 self.nodelist = nodelist 12 self.expire_time_var = Variable(expire_time_var)12 self.expire_time_var = expire_time_var 13 13 self.fragment_name = fragment_name 14 14 self.vary_on = vary_on 15 15 … … 23 23 except (ValueError, TypeError): 24 24 raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time) 25 25 # Build a unicode key for this fragment and all vary-on's. 26 cache_key = u':'.join([self.fragment_name] + [urlquote( resolve_variable(var,context)) for var in self.vary_on])26 cache_key = u':'.join([self.fragment_name] + [urlquote(var.resolve_safe(context)) for var in self.vary_on]) 27 27 value = cache.get(cache_key) 28 28 if value is None: 29 29 value = self.nodelist.render(context) 30 30 cache.set(cache_key, value, expire_time) 31 31 return value 32 32 33 def do_cache(parser, token):33 def do_cache(parser, bits): 34 34 """ 35 35 This will cache the contents of a template fragment for a given amount 36 36 of time. … … 51 51 52 52 Each unique set of arguments will result in a unique cache entry. 53 53 """ 54 nodelist = parser.parse(('endcache',)) 55 parser.delete_first_token() 56 tokens = token.contents.split() 57 if len(tokens) < 3: 58 raise TemplateSyntaxError(u"'%r' tag requires at least 2 arguments." % tokens[0]) 59 return CacheNode(nodelist, tokens[1], tokens[2], tokens[3:]) 54 nodelist = parser.parse_nodelist(('endcache',)) 55 expire_time = bits.parse_expression(required=True) 56 name = bits.pop_name() 57 if not name: 58 raise TemplateSyntaxError, "'cache' requires a fragment name" 59 vary_on = bits.parse_expression_list() 60 return CacheNode(nodelist, expire_time, name, vary_on) 60 61 61 register.tag('cache', do_cache)62 register.tag('cache', uses_token_stream(do_cache)) -
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 __repr__(self): 17 return "<%s>" % self.__class__.__name__ 18 19 def __iter__(self): 20 yield self 21 22 def get_nodes_by_type(self, nodetype): 23 "Return a list of all nodes (within this node and its nodelist) of the given type" 24 nodes = [] 25 if isinstance(self, nodetype): 26 nodes.append(self) 27 if hasattr(self, 'nodelist'): 28 nodes.extend(self.nodelist.get_nodes_by_type(nodetype)) 29 return nodes 30 31 class NodeList(list): 32 # Set to True the first time a non-TextNode is inserted by 33 # extend_nodelist(). 34 contains_nontext = False 35 36 def render(self, context): 37 bits = [] 38 for node in self: 39 if isinstance(node, Node): 40 bits.append(self.render_node(node, context)) 41 else: 42 bits.append(node) 43 return mark_safe(''.join([force_unicode(b) for b in bits])) 44 45 def get_nodes_by_type(self, nodetype): 46 "Return a list of all nodes of the given type" 47 nodes = [] 48 for node in self: 49 nodes.extend(node.get_nodes_by_type(nodetype)) 50 return nodes 51 52 def render_node(self, node, context): 53 return node.render(context) 54 55 class TextNode(Node): 56 def __init__(self, s): 57 self.s = s 58 59 def __repr__(self): 60 return "<Text Node: '%s'>" % self.s[:25] 61 62 def render(self, context): 63 return self.s 64 65 class ExpressionNode(Node): 66 def __init__(self, expression): 67 self.expression = expression 68 69 def __repr__(self): 70 return "<Variable Node: %s>" % self.expression 71 72 def render(self, context): 73 try: 74 output = force_unicode(self.expression.resolve(context)) 75 except LookupError: 76 if settings.TEMPLATE_STRING_IF_INVALID: 77 from django.template import invalid_var_format_string 78 if invalid_var_format_string is None: 79 invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID 80 if invalid_var_format_string: 81 return settings.TEMPLATE_STRING_IF_INVALID % self.expression 82 return settings.TEMPLATE_STRING_IF_INVALID 83 else: 84 return u'' 85 except UnicodeDecodeError: 86 # Unicode conversion can fail sometimes for reasons out of our 87 # control (e.g. exception rendering). In that case, we fail quietly. 88 return u'' 89 if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData): 90 output = escape(output) 91 return output -
django/template/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 6 from django.template.nodes import Node, NodeList, ExpressionNode, TextNode 7 from django.utils.encoding import smart_unicode, smart_str 8 from django.utils.functional import wraps 9 from django.utils.safestring import mark_safe 10 from django.utils.text import smart_split 11 from django.utils.translation import ugettext 12 13 __all__ = ('Template', 'TemplateSyntaxError', 'TokenSyntaxError', 'TokenStream') 14 15 TOKEN_TEXT = 0 16 TOKEN_VAR = 1 17 TOKEN_BLOCK = 2 18 TOKEN_COMMENT = 3 19 20 # template syntax constants 21 BLOCK_TAG_START = '{%' 22 BLOCK_TAG_END = '%}' 23 VARIABLE_TAG_START = '{{' 24 VARIABLE_TAG_END = '}}' 25 COMMENT_TAG_START = '{#' 26 COMMENT_TAG_END = '#}' 27 SINGLE_BRACE_START = '{' 28 SINGLE_BRACE_END = '}' 29 FILTER_SEPARATOR = '|' 30 FILTER_ARGUMENT_SEPARATOR = ':' 31 VARIABLE_ATTRIBUTE_SEPARATOR = '.' 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 split = [] 122 bits = iter(smart_split(self.contents)) 123 for bit in bits: 124 # Handle translation-marked template pieces 125 if bit.startswith('_("') or bit.startswith("_('"): 126 sentinal = bit[2] + ')' 127 trans_bit = [bit] 128 while not bit.endswith(sentinal): 129 bit = bits.next() 130 trans_bit.append(bit) 131 bit = ' '.join(trans_bit) 132 split.append(bit) 133 return split 134 135 class Lexer(object): 136 def __init__(self, template_string, origin): 137 self.template_string = template_string 138 self.origin = origin 139 140 def tokenize(self): 141 "Return a list of tokens from a given template_string." 142 in_tag = False 143 result = [] 144 for bit in tag_re.split(self.template_string): 145 if bit: 146 result.append(self.create_token(bit, in_tag)) 147 in_tag = not in_tag 148 return result 149 150 def create_token(self, token_string, in_tag): 151 """ 152 Convert the given token string into a new Token object and return it. 153 If in_tag is True, we are processing something that matched a tag, 154 otherwise it should be treated as a literal string. 155 """ 156 if in_tag: 157 if token_string.startswith(VARIABLE_TAG_START): 158 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) 159 elif token_string.startswith(BLOCK_TAG_START): 160 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) 161 elif token_string.startswith(COMMENT_TAG_START): 162 token = Token(TOKEN_COMMENT, '') 163 else: 164 token = Token(TOKEN_TEXT, token_string) 165 return token 166 167 class Parser(object): 168 def __init__(self, tokens): 169 self.tokens = tokens 170 self.tags = {} 171 self.filters = {} 172 from django.template.library import builtins 173 for lib in builtins: 174 self.add_library(lib) 175 176 def parse(self, parse_until=None): 177 if parse_until is None: parse_until = [] 178 nodelist = self.create_nodelist() 179 while self.tokens: 180 token = self.next_token() 181 if token.token_type == TOKEN_TEXT: 182 self.extend_nodelist(nodelist, TextNode(token.contents), token) 183 elif token.token_type == TOKEN_VAR: 184 if not token.contents: 185 self.empty_variable(token) 186 filter_expression = self.compile_filter(token.contents) 187 var_node = self.create_variable_node(filter_expression) 188 self.extend_nodelist(nodelist, var_node,token) 189 elif token.token_type == TOKEN_BLOCK: 190 if token.contents in parse_until: 191 # put token back on token list so calling code knows why it terminated 192 self.prepend_token(token) 193 return nodelist 194 try: 195 command = token.contents.split()[0] 196 except IndexError: 197 self.empty_block_tag(token) 198 # execute callback function for this tag and append resulting node 199 self.enter_command(command, token) 200 try: 201 compile_func = self.tags[command] 202 except KeyError: 203 self.invalid_block_tag(token, command) 204 try: 205 compiled_result = compile_func(self, token) 206 except TemplateSyntaxError, e: 207 if not self.compile_function_error(token, e): 208 raise 209 self.extend_nodelist(nodelist, compiled_result, token) 210 self.exit_command() 211 if parse_until: 212 self.unclosed_block_tag(parse_until) 213 return nodelist 214 215 def parse_nodelist(self, parse_until=None): 216 nodelist = self.parse(parse_until=parse_until) 217 self.delete_first_token() 218 return nodelist 219 220 def skip_past(self, endtag): 221 while self.tokens: 222 token = self.next_token() 223 if token.token_type == TOKEN_BLOCK and token.contents == endtag: 224 return 225 self.unclosed_block_tag([endtag]) 226 227 def create_variable_node(self, expression): 228 return ExpressionNode(expression) 229 230 def create_nodelist(self): 231 return NodeList() 232 233 def extend_nodelist(self, nodelist, node, token): 234 if node.must_be_first and nodelist: 235 try: 236 if nodelist.contains_nontext: 237 raise AttributeError 238 except AttributeError: 239 raise TemplateSyntaxError("%r must be the first tag in the template." % node) 240 if isinstance(nodelist, NodeList) and not isinstance(node, TextNode): 241 nodelist.contains_nontext = True 242 nodelist.append(node) 243 244 def enter_command(self, command, token): 245 pass 246 247 def exit_command(self): 248 pass 249 250 def error(self, token, msg): 251 return TemplateSyntaxError(msg) 252 253 def empty_variable(self, token): 254 raise self.error(token, "Empty variable tag") 255 256 def empty_block_tag(self, token): 257 raise self.error(token, "Empty block tag") 258 259 def invalid_block_tag(self, token, command): 260 raise self.error(token, "Invalid block tag: '%s'" % command) 261 262 def unclosed_block_tag(self, parse_until): 263 raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until)) 264 265 def compile_function_error(self, token, e): 266 pass 267 268 def next_token(self): 269 return self.tokens.pop(0) 270 271 def prepend_token(self, token): 272 self.tokens.insert(0, token) 273 274 def delete_first_token(self): 275 del self.tokens[0] 276 277 def add_library(self, lib): 278 self.tags.update(lib.tags) 279 self.filters.update(lib.filters) 280 281 def token_stream(self, token): 282 return TokenStream(self, token) 283 284 def compile_filter(self, token): 285 stream = self.token_stream(token) 286 expr = stream.parse_expression(required=True) 287 if not stream.consumed(): 288 raise TemplateSyntaxError("Invalid filter expression") 289 return expr 290 291 def find_filter(self, filter_name): 292 if filter_name in self.filters: 293 return self.filters[filter_name] 294 else: 295 raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name) 296 297 def filter_args_check(name, func, provided): 298 provided = list(provided) 299 plen = len(provided) 300 # Check to see if a decorator is providing the real function. 301 func = getattr(func, '_decorated_function', func) 302 args, varargs, varkw, defaults = getargspec(func) 303 304 if plen + 1 == len(args) or (defaults and plen + 1 <= len(args) + len(defaults)): 305 return True 306 307 # First argument is filter input. 308 args.pop(0) 309 if defaults: 310 nondefs = args[:-len(defaults)] 311 else: 312 nondefs = args 313 # Args without defaults must be provided. 314 try: 315 for arg in nondefs: 316 provided.pop(0) 317 except IndexError: 318 # Not enough 319 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen)) 320 321 # Defaults can be overridden. 322 defaults = defaults and list(defaults) or [] 323 try: 324 for parg in provided: 325 defaults.pop(0) 326 except IndexError: 327 # Too many. 328 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen)) 329 330 return True 331 332 EOT = '<EOT>' 333 punctuation_chars = r':|=,;<>!?%&@"\'/()\[\]{}`*+-' 334 bit_re = re.compile(r""" 335 (?P<string_literal>"(?:[^"\\]*(?:\\.[^"\\]*)*)"|'(?:[^'\\]*(?:\\.[^'\\]*)*)') 336 |(?P<numeric_literal>[+-]?\.?\d[\d\.e]*) 337 |(?P<char>[%s]) # punctuation 338 |(?P<name>[^\s%s]+) # keyword or variable 339 """ % ((punctuation_chars,) * 2), re.VERBOSE) 340 STRING_LITERAL = "string_literal" #1 341 NUMERIC_LITERAL = "numeric_literal" #2 342 CHAR = "char" #3 343 NAME = "name" #4 344 345 def unescape_string_literal(literal): 346 q = literal[0] 347 return literal.replace(r'\%s' % q, q).replace(r'\\', '')[1:-1] 348 349 class TokenSyntaxError(Exception): 350 pass 351 352 def token_stream_parser(func): 353 def wrapper(self, required=False, *args, **kwargs): 354 mark = self.offset 355 try: 356 return func(self, *args, **kwargs) 357 except TokenSyntaxError: 358 if required: 359 #FIXME: hack 360 self.expected("<%s>" % ' '.join(func.__name__.split('_')[1:])) 361 self.offset = mark 362 raise 363 return wraps(func)(wrapper) 364 365 class TokenStream(object): 366 def __init__(self, parser, source): 367 self.parser = parser 368 self.source = source 369 self.offset = 0 370 self.name = None 371 self.token = None 372 if isinstance(source, Token): 373 bits = source.contents.split(None, 1) 374 self.source = len(bits) == 2 and bits[1] or '' 375 self.token = source 376 self.name = bits[0] 377 self.tokens = [(bit.lastgroup, bit.group(0)) for bit in bit_re.finditer(self.source)] 378 379 def consumed(self): 380 return self.offset == len(self.tokens) 381 382 def pop(self): 383 if self.offset == len(self.tokens): 384 raise TokenSyntaxError 385 next = self.tokens[self.offset] 386 self.offset += 1 387 return next 388 389 def pop_lexem(self, match): 390 if self.offset == len(self.tokens): 391 return False 392 tokentype, lexem = self.tokens[self.offset] 393 if lexem == match: 394 self.offset += 1 395 return True 396 return False 397 398 def pop_name(self): 399 if self.offset == len(self.tokens): 400 return None 401 tokentype, lexem = self.tokens[self.offset] 402 if tokentype == NAME: 403 self.offset += 1 404 return lexem 405 return None 406 407 def pushback(self): 408 self.offset -= 1 409 410 def syntax_error(self, msg): 411 if self.name: 412 msg = u"{%% %s %%} %s" % (self.name, msg) 413 raise TemplateSyntaxError(msg) 414 415 def expected(self, what): 416 if self.consumed(): 417 found = EOT 418 else: 419 found = "<%s> %s" % self.tokens[self.offset] 420 self.syntax_error("expected %s, found %s" % (what, smart_str(found, encoding='ascii', errors='backslashreplace'))) 421 422 #@token_stream_parser 423 def parse_string(self, bare=False): 424 tokentype, lexem = self.pop() 425 if tokentype == STRING_LITERAL: 426 return unescape_string_literal(lexem) 427 if bare and tokentype == NAME: 428 return lexem 429 raise TokenSyntaxError 430 parse_string = token_stream_parser(parse_string) 431 432 #@token_stream_parser 433 def parse_int(self): 434 token_type, lexem = self.pop() 435 if token_type == NUMERIC_LITERAL: 436 try: 437 return int(lexem) 438 except ValueError: 439 pass 440 raise TokenSyntaxError 441 parse_int = token_stream_parser(parse_int) 442 443 #@token_stream_parser 444 def parse_value(self): 445 translate = False 446 if self.pop_lexem('_'): 447 if not self.pop_lexem('('): 448 raise TokenSyntaxError 449 translate = True 450 451 tokentype, lexem = self.pop() 452 if tokentype == NAME: 453 if lexem.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or lexem[0] == '_': 454 raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % lexem) 455 value = Lookup(tuple(lexem.split(VARIABLE_ATTRIBUTE_SEPARATOR)), var=lexem) 456 elif tokentype == STRING_LITERAL: 457 value = Literal(mark_safe(unescape_string_literal(lexem))) 458 elif tokentype == NUMERIC_LITERAL: 459 try: 460 value = float(lexem) 461 except ValueError: 462 raise TokenSyntaxError 463 if '.' not in lexem and 'e' not in lexem.lower(): 464 value = int(value) 465 #FIXME: this causes a test failure: `ifequal-numeric07` 466 if lexem.endswith('.'): 467 raise TemplateSyntaxError, "Numeric literals may not end with '.': %s" % lexem 468 value = Literal(value) 469 elif tokentype == CHAR: 470 raise TokenSyntaxError 471 472 if translate: 473 if not self.pop_lexem(')'): 474 raise TokenSyntaxError 475 # Don't pass the empty string to gettext, because the empty 476 # string translates to meta information 477 value = FilterExpression(value, [(lambda x: x and ugettext(x) or u'', ())]) 478 return value 479 parse_value = token_stream_parser(parse_value) 480 481 #@token_stream_parser 482 def parse_filter(self): 483 if not self.pop_lexem('|'): 484 raise TokenSyntaxError 485 name = self.pop_name() 486 if not name: 487 raise TokenSyntaxError 488 args = [] 489 if self.pop_lexem(':'): 490 args.append(self.parse_value()) 491 func = self.parser.find_filter(name) 492 filter_args_check(name, func, args) 493 return func, args 494 parse_filter = token_stream_parser(parse_filter) 495 496 #@token_stream_parser 497 def parse_expression(self): 498 var = self.parse_value() 499 filters = [] 500 try: 501 while True: 502 filters.append(self.parse_filter()) 503 except TokenSyntaxError: 504 pass 505 if filters: 506 return FilterExpression(var, filters) 507 return var 508 parse_expression = token_stream_parser(parse_expression) 509 510 #@token_stream_parser 511 def parse_expression_list(self, minimum=0, maximum=None, count=None): 512 expressions = [] 513 if count: 514 minimum = count 515 maximum = count 516 try: 517 while True: 518 if len(expressions) == maximum: 519 break 520 expressions.append(self.parse_expression()) 521 except TokenSyntaxError: 522 pass 523 if len(expressions) < minimum: 524 self.expected("expression") 525 return expressions 526 parse_expression_list = token_stream_parser(parse_expression_list) 527 528 529 def uses_token_stream(func): 530 def decorator(parser, token): 531 bits = parser.token_stream(token) 532 result = func(parser, bits) 533 if bits.offset != len(bits.tokens): 534 bits.expected(EOT) 535 return result 536 return wraps(func)(decorator) 537 -
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 split = []201 bits = iter(smart_split(self.contents))202 for bit in bits:203 # Handle translation-marked template pieces204 if bit.startswith('_("') or bit.startswith("_('"):205 sentinal = bit[2] + ')'206 trans_bit = [bit]207 while not bit.endswith(sentinal):208 bit = bits.next()209 trans_bit.append(bit)210 bit = ' '.join(trans_bit)211 split.append(bit)212 return split213 214 class Lexer(object):215 def __init__(self, template_string, origin):216 self.template_string = template_string217 self.origin = origin218 219 def tokenize(self):220 "Return a list of tokens from a given template_string."221 in_tag = False222 result = []223 for bit in tag_re.split(self.template_string):224 if bit:225 result.append(self.create_token(bit, in_tag))226 in_tag = not in_tag227 return result228 229 def create_token(self, token_string, in_tag):230 """231 Convert the given token string into a new Token object and return it.232 If in_tag is True, we are processing something that matched a tag,233 otherwise it should be treated as a literal string.234 """235 if in_tag:236 if token_string.startswith(VARIABLE_TAG_START):237 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())238 elif token_string.startswith(BLOCK_TAG_START):239 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())240 elif token_string.startswith(COMMENT_TAG_START):241 token = Token(TOKEN_COMMENT, '')242 else:243 token = Token(TOKEN_TEXT, token_string)244 return token245 246 class Parser(object):247 def __init__(self, tokens):248 self.tokens = tokens249 self.tags = {}250 self.filters = {}251 for lib in builtins:252 self.add_library(lib)253 254 def parse(self, parse_until=None):255 if parse_until is None: parse_until = []256 nodelist = self.create_nodelist()257 while self.tokens:258 token = self.next_token()259 if token.token_type == TOKEN_TEXT:260 self.extend_nodelist(nodelist, TextNode(token.contents), token)261 elif token.token_type == TOKEN_VAR:262 if not token.contents:263 self.empty_variable(token)264 filter_expression = self.compile_filter(token.contents)265 var_node = self.create_variable_node(filter_expression)266 self.extend_nodelist(nodelist, var_node,token)267 elif token.token_type == TOKEN_BLOCK:268 if token.contents in parse_until:269 # put token back on token list so calling code knows why it terminated270 self.prepend_token(token)271 return nodelist272 try:273 command = token.contents.split()[0]274 except IndexError:275 self.empty_block_tag(token)276 # execute callback function for this tag and append resulting node277 self.enter_command(command, token)278 try:279 compile_func = self.tags[command]280 except KeyError:281 self.invalid_block_tag(token, command)282 try:283 compiled_result = compile_func(self, token)284 except TemplateSyntaxError, e:285 if not self.compile_function_error(token, e):286 raise287 self.extend_nodelist(nodelist, compiled_result, token)288 self.exit_command()289 if parse_until:290 self.unclosed_block_tag(parse_until)291 return nodelist292 293 def skip_past(self, endtag):294 while self.tokens:295 token = self.next_token()296 if token.token_type == TOKEN_BLOCK and token.contents == endtag:297 return298 self.unclosed_block_tag([endtag])299 300 def create_variable_node(self, filter_expression):301 return VariableNode(filter_expression)302 303 def create_nodelist(self):304 return NodeList()305 306 def extend_nodelist(self, nodelist, node, token):307 if node.must_be_first and nodelist:308 try:309 if nodelist.contains_nontext:310 raise AttributeError311 except AttributeError:312 raise TemplateSyntaxError("%r must be the first tag in the template." % node)313 if isinstance(nodelist, NodeList) and not isinstance(node, TextNode):314 nodelist.contains_nontext = True315 nodelist.append(node)316 317 def enter_command(self, command, token):318 pass319 320 def exit_command(self):321 pass322 323 def error(self, token, msg):324 return TemplateSyntaxError(msg)325 326 def empty_variable(self, token):327 raise self.error(token, "Empty variable tag")328 329 def empty_block_tag(self, token):330 raise self.error(token, "Empty block tag")331 332 def invalid_block_tag(self, token, command):333 raise self.error(token, "Invalid block tag: '%s'" % command)334 335 def unclosed_block_tag(self, parse_until):336 raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until))337 338 def compile_function_error(self, token, e):339 pass340 341 def next_token(self):342 return self.tokens.pop(0)343 344 def prepend_token(self, token):345 self.tokens.insert(0, token)346 347 def delete_first_token(self):348 del self.tokens[0]349 350 def add_library(self, lib):351 self.tags.update(lib.tags)352 self.filters.update(lib.filters)353 354 def compile_filter(self, token):355 "Convenient wrapper for FilterExpression"356 return FilterExpression(token, self)357 358 def find_filter(self, filter_name):359 if filter_name in self.filters:360 return self.filters[filter_name]361 else:362 raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)363 364 class TokenParser(object):365 """366 Subclass this and implement the top() method to parse a template line. When367 instantiating the parser, pass in the line from the Django template parser.368 369 The parser's "tagname" instance-variable stores the name of the tag that370 the filter was called with.371 """372 def __init__(self, subject):373 self.subject = subject374 self.pointer = 0375 self.backout = []376 self.tagname = self.tag()377 378 def top(self):379 "Overload this method to do the actual parsing and return the result."380 raise NotImplementedError()381 382 def more(self):383 "Returns True if there is more stuff in the tag."384 return self.pointer < len(self.subject)385 386 def back(self):387 "Undoes the last microparser. Use this for lookahead and backtracking."388 if not len(self.backout):389 raise TemplateSyntaxError("back called without some previous parsing")390 self.pointer = self.backout.pop()391 392 def tag(self):393 "A microparser that just returns the next tag from the line."394 subject = self.subject395 i = self.pointer396 if i >= len(subject):397 raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject)398 p = i399 while i < len(subject) and subject[i] not in (' ', '\t'):400 i += 1401 s = subject[p:i]402 while i < len(subject) and subject[i] in (' ', '\t'):403 i += 1404 self.backout.append(self.pointer)405 self.pointer = i406 return s407 408 def value(self):409 "A microparser that parses for a value: some string constant or variable name."410 subject = self.subject411 i = self.pointer412 if i >= len(subject):413 raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject)414 if subject[i] in ('"', "'"):415 p = i416 i += 1417 while i < len(subject) and subject[i] != subject[p]:418 i += 1419 if i >= len(subject):420 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))421 i += 1422 res = subject[p:i]423 while i < len(subject) and subject[i] in (' ', '\t'):424 i += 1425 self.backout.append(self.pointer)426 self.pointer = i427 return res428 else:429 p = i430 while i < len(subject) and subject[i] not in (' ', '\t'):431 if subject[i] in ('"', "'"):432 c = subject[i]433 i += 1434 while i < len(subject) and subject[i] != c:435 i += 1436 if i >= len(subject):437 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject))438 i += 1439 s = subject[p:i]440 while i < len(subject) and subject[i] in (' ', '\t'):441 i += 1442 self.backout.append(self.pointer)443 self.pointer = i444 return s445 446 filter_raw_string = r"""447 ^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|448 ^"(?P<constant>%(str)s)"|449 ^(?P<var>[%(var_chars)s]+)|450 (?:%(filter_sep)s451 (?P<filter_name>\w+)452 (?:%(arg_sep)s453 (?:454 %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|455 "(?P<constant_arg>%(str)s)"|456 (?P<var_arg>[%(var_chars)s]+)457 )458 )?459 )""" % {460 'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",461 'var_chars': "\w\." ,462 'filter_sep': re.escape(FILTER_SEPARATOR),463 'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),464 'i18n_open' : re.escape("_("),465 'i18n_close' : re.escape(")"),466 }467 468 filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")469 filter_re = re.compile(filter_raw_string, re.UNICODE)470 471 class FilterExpression(object):472 """473 Parses a variable token and its optional filters (all as a single string),474 and return a list of tuples of the filter name and arguments.475 Sample:476 >>> token = 'variable|default:"Default value"|date:"Y-m-d"'477 >>> p = Parser('')478 >>> fe = FilterExpression(token, p)479 >>> len(fe.filters)480 2481 >>> fe.var482 <Variable: 'variable'>483 484 This class should never be instantiated outside of the485 get_filters_from_token helper function.486 """487 def __init__(self, token, parser):488 self.token = token489 matches = filter_re.finditer(token)490 var = None491 filters = []492 upto = 0493 for match in matches:494 start = match.start()495 if upto != start:496 raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s" % \497 (token[:upto], token[upto:start], token[start:]))498 if var == None:499 var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")500 if i18n_constant is not None:501 # Don't pass the empty string to gettext, because the empty502 # string translates to meta information.503 if i18n_constant == "":504 var = '""'505 else:506 var = '"%s"' % _(i18n_constant.replace(r'\"', '"'))507 elif constant is not None:508 var = '"%s"' % constant.replace(r'\"', '"')509 upto = match.end()510 if var == None:511 raise TemplateSyntaxError("Could not find variable at start of %s" % token)512 elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':513 raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var)514 else:515 filter_name = match.group("filter_name")516 args = []517 constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg")518 if i18n_arg:519 args.append((False, _(i18n_arg.replace(r'\"', '"'))))520 elif constant_arg is not None:521 args.append((False, constant_arg.replace(r'\"', '"')))522 elif var_arg:523 args.append((True, Variable(var_arg)))524 filter_func = parser.find_filter(filter_name)525 self.args_check(filter_name,filter_func, args)526 filters.append( (filter_func,args))527 upto = match.end()528 if upto != len(token):529 raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token))530 self.filters = filters531 self.var = Variable(var)532 533 def resolve(self, context, ignore_failures=False):534 try:535 obj = self.var.resolve(context)536 except VariableDoesNotExist:537 if ignore_failures:538 obj = None539 else:540 if settings.TEMPLATE_STRING_IF_INVALID:541 global invalid_var_format_string542 if invalid_var_format_string is None:543 invalid_var_format_string = '%s' in settings.TEMPLATE_STRING_IF_INVALID544 if invalid_var_format_string:545 return settings.TEMPLATE_STRING_IF_INVALID % self.var546 return settings.TEMPLATE_STRING_IF_INVALID547 else:548 obj = settings.TEMPLATE_STRING_IF_INVALID549 for func, args in self.filters:550 arg_vals = []551 for lookup, arg in args:552 if not lookup:553 arg_vals.append(mark_safe(arg))554 else:555 arg_vals.append(arg.resolve(context))556 if getattr(func, 'needs_autoescape', False):557 new_obj = func(obj, autoescape=context.autoescape, *arg_vals)558 else:559 new_obj = func(obj, *arg_vals)560 if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):561 obj = mark_safe(new_obj)562 elif isinstance(obj, EscapeData):563 obj = mark_for_escaping(new_obj)564 else:565 obj = new_obj566 return obj567 568 def args_check(name, func, provided):569 provided = list(provided)570 plen = len(provided)571 # Check to see if a decorator is providing the real function.572 func = getattr(func, '_decorated_function', func)573 args, varargs, varkw, defaults = getargspec(func)574 # First argument is filter input.575 args.pop(0)576 if defaults:577 nondefs = args[:-len(defaults)]578 else:579 nondefs = args580 # Args without defaults must be provided.581 try:582 for arg in nondefs:583 provided.pop(0)584 except IndexError:585 # Not enough586 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))587 588 # Defaults can be overridden.589 defaults = defaults and list(defaults) or []590 try:591 for parg in provided:592 defaults.pop(0)593 except IndexError:594 # Too many.595 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen))596 597 return True598 args_check = staticmethod(args_check)599 600 def __str__(self):601 return self.token602 603 def resolve_variable(path, context):604 """605 Returns the resolved variable, which may contain attribute syntax, within606 the given context.607 608 Deprecated; use the Variable class instead.609 """610 return Variable(path).resolve(context)611 612 class Variable(object):613 """614 A template variable, resolvable against a given context. The variable may be615 a hard-coded string (if it begins and ends with single or double quote616 marks)::617 618 >>> c = {'article': {'section':u'News'}}619 >>> Variable('article.section').resolve(c)620 u'News'621 >>> Variable('article').resolve(c)622 {'section': u'News'}623 >>> class AClass: pass624 >>> c = AClass()625 >>> c.article = AClass()626 >>> c.article.section = u'News'627 >>> Variable('article.section').resolve(c)628 u'News'629 630 (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.')631 """632 633 def __init__(self, var):634 self.var = var635 self.literal = None636 self.lookups = None637 self.translate = False638 639 try:640 # First try to treat this variable as a number.641 #642 # Note that this could cause an OverflowError here that we're not643 # catching. Since this should only happen at compile time, that's644 # probably OK.645 self.literal = float(var)646 647 # So it's a float... is it an int? If the original value contained a648 # dot or an "e" then it was a float, not an int.649 if '.' not in var and 'e' not in var.lower():650 self.literal = int(self.literal)651 652 # "2." is invalid653 if var.endswith('.'):654 raise ValueError655 656 except ValueError:657 # A ValueError means that the variable isn't a number.658 if var.startswith('_(') and var.endswith(')'):659 # The result of the lookup should be translated at rendering660 # time.661 self.translate = True662 var = var[2:-1]663 # If it's wrapped with quotes (single or double), then664 # we're also dealing with a literal.665 if var[0] in "\"'" and var[0] == var[-1]:666 self.literal = mark_safe(var[1:-1])667 else:668 # Otherwise we'll set self.lookups so that resolve() knows we're669 # dealing with a bonafide variable670 self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR))671 672 def resolve(self, context):673 """Resolve this variable against a given context."""674 if self.lookups is not None:675 # We're dealing with a variable that needs to be resolved676 value = self._resolve_lookup(context)677 else:678 # We're dealing with a literal, so it's already been "resolved"679 value = self.literal680 if self.translate:681 return _(value)682 return value683 684 def __repr__(self):685 return "<%s: %r>" % (self.__class__.__name__, self.var)686 687 def __str__(self):688 return self.var689 690 def _resolve_lookup(self, context):691 """692 Performs resolution of a real variable (i.e. not a literal) against the693 given context.694 695 As indicated by the method's name, this method is an implementation696 detail and shouldn't be called by external code. Use Variable.resolve()697 instead.698 """699 current = context700 for bit in self.lookups:701 try: # dictionary lookup702 current = current[bit]703 except (TypeError, AttributeError, KeyError):704 try: # attribute lookup705 current = getattr(current, bit)706 if callable(current):707 if getattr(current, 'alters_data', False):708 current = settings.TEMPLATE_STRING_IF_INVALID709 else:710 try: # method call (assuming no args required)711 current = current()712 except TypeError: # arguments *were* required713 # GOTCHA: This will also catch any TypeError714 # raised in the function itself.715 current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call716 except Exception, e:717 if getattr(e, 'silent_variable_failure', False):718 current = settings.TEMPLATE_STRING_IF_INVALID719 else:720 raise721 except (TypeError, AttributeError):722 try: # list-index lookup723 current = current[int(bit)]724 except (IndexError, # list index out of range725 ValueError, # invalid literal for int()726 KeyError, # current is a dict without `int(bit)` key727 TypeError, # unsubscriptable object728 ):729 raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute730 except Exception, e:731 if getattr(e, 'silent_variable_failure', False):732 current = settings.TEMPLATE_STRING_IF_INVALID733 else:734 raise735 736 return current737 738 class Node(object):739 # Set this to True for nodes that must be first in the template (although740 # they can be preceded by text nodes.741 must_be_first = False742 743 def render(self, context):744 "Return the node rendered as a string"745 pass746 747 def __iter__(self):748 yield self749 750 def get_nodes_by_type(self, nodetype):751 "Return a list of all nodes (within this node and its nodelist) of the given type"752 nodes = []753 if isinstance(self, nodetype):754 nodes.append(self)755 if hasattr(self, 'nodelist'):756 nodes.extend(self.nodelist.get_nodes_by_type(nodetype))757 return nodes758 759 class NodeList(list):760 # Set to True the first time a non-TextNode is inserted by761 # extend_nodelist().762 contains_nontext = False763 764 def render(self, context):765 bits = []766 for node in self:767 if isinstance(node, Node):768 bits.append(self.render_node(node, context))769 else:770 bits.append(node)771 return mark_safe(''.join([force_unicode(b) for b in bits]))772 773 def get_nodes_by_type(self, nodetype):774 "Return a list of all nodes of the given type"775 nodes = []776 for node in self:777 nodes.extend(node.get_nodes_by_type(nodetype))778 return nodes779 780 def render_node(self, node, context):781 return node.render(context)782 783 class TextNode(Node):784 def __init__(self, s):785 self.s = s786 787 def __repr__(self):788 return "<Text Node: '%s'>" % self.s[:25]789 790 def render(self, context):791 return self.s792 793 class VariableNode(Node):794 def __init__(self, filter_expression):795 self.filter_expression = filter_expression796 797 def __repr__(self):798 return "<Variable Node: %s>" % self.filter_expression799 800 def render(self, context):801 try:802 output = force_unicode(self.filter_expression.resolve(context))803 except UnicodeDecodeError:804 # Unicode conversion can fail sometimes for reasons out of our805 # control (e.g. exception rendering). In that case, we fail quietly.806 return ''807 if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):808 return force_unicode(escape(output))809 else:810 return force_unicode(output)811 812 def generic_tag_compiler(params, defaults, name, node_class, parser, token):813 "Returns a template.Node subclass."814 bits = token.split_contents()[1:]815 bmax = len(params)816 def_len = defaults and len(defaults) or 0817 bmin = bmax - def_len818 if(len(bits) < bmin or len(bits) > bmax):819 if bmin == bmax:820 message = "%s takes %s arguments" % (name, bmin)821 else:822 message = "%s takes between %s and %s arguments" % (name, bmin, bmax)823 raise TemplateSyntaxError(message)824 return node_class(bits)825 826 class Library(object):827 def __init__(self):828 self.filters = {}829 self.tags = {}830 831 def tag(self, name=None, compile_function=None):832 if name == None and compile_function == None:833 # @register.tag()834 return self.tag_function835 elif name != None and compile_function == None:836 if(callable(name)):837 # @register.tag838 return self.tag_function(name)839 else:840 # @register.tag('somename') or @register.tag(name='somename')841 def dec(func):842 return self.tag(name, func)843 return dec844 elif name != None and compile_function != None:845 # register.tag('somename', somefunc)846 self.tags[name] = compile_function847 return compile_function848 else:849 raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function))850 851 def tag_function(self,func):852 self.tags[getattr(func, "_decorated_function", func).__name__] = func853 return func854 855 def filter(self, name=None, filter_func=None):856 if name == None and filter_func == None:857 # @register.filter()858 return self.filter_function859 elif filter_func == None:860 if(callable(name)):861 # @register.filter862 return self.filter_function(name)863 else:864 # @register.filter('somename') or @register.filter(name='somename')865 def dec(func):866 return self.filter(name, func)867 return dec868 elif name != None and filter_func != None:869 # register.filter('somename', somefunc)870 self.filters[name] = filter_func871 return filter_func872 else:873 raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func))874 875 def filter_function(self, func):876 self.filters[getattr(func, "_decorated_function", func).__name__] = func877 return func878 879 def simple_tag(self,func):880 params, xx, xxx, defaults = getargspec(func)881 882 class SimpleNode(Node):883 def __init__(self, vars_to_resolve):884 self.vars_to_resolve = map(Variable, vars_to_resolve)885 886 def render(self, context):887 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]888 return func(*resolved_vars)889 890 compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, SimpleNode)891 compile_func.__doc__ = func.__doc__892 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)893 return func894 895 def inclusion_tag(self, file_name, context_class=Context, takes_context=False):896 def dec(func):897 params, xx, xxx, defaults = getargspec(func)898 if takes_context:899 if params[0] == 'context':900 params = params[1:]901 else:902 raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'")903 904 class InclusionNode(Node):905 def __init__(self, vars_to_resolve):906 self.vars_to_resolve = map(Variable, vars_to_resolve)907 908 def render(self, context):909 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve]910 if takes_context:911 args = [context] + resolved_vars912 else:913 args = resolved_vars914 915 dict = func(*args)916 917 if not getattr(self, 'nodelist', False):918 from django.template.loader import get_template, select_template919 if not isinstance(file_name, basestring) and is_iterable(file_name):920 t = select_template(file_name)921 else:922 t = get_template(file_name)923 self.nodelist = t.nodelist924 return self.nodelist.render(context_class(dict,925 autoescape=context.autoescape))926 927 compile_func = curry(generic_tag_compiler, params, defaults, getattr(func, "_decorated_function", func).__name__, InclusionNode)928 compile_func.__doc__ = func.__doc__929 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func)930 return func931 return dec932 933 def get_library(module_name):934 lib = libraries.get(module_name, None)935 if not lib:936 try:937 mod = __import__(module_name, {}, {}, [''])938 except ImportError, e:939 raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e))940 try:941 lib = mod.register942 libraries[module_name] = lib943 except AttributeError:944 raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name)945 return lib946 947 def add_to_builtins(module_name):948 builtins.append(get_library(module_name))949 950 79 add_to_builtins('django.template.defaulttags') 951 80 add_to_builtins('django.template.defaultfilters') -
django/template/utils.py
1 import re 2 from django.template import Node, NodeList, TokenSyntaxError, TemplateSyntaxError 3 4 class EmptyNode(Node): 5 def render(self, context): 6 return u'' 7 8 class ConditionalNode(Node): 9 def __init__(self, nodelist_true, nodelist_false): 10 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 11 12 def __iter__(self): 13 for node in self.nodelist_true: 14 yield node 15 for node in self.nodelist_false: 16 yield node 17 18 def get_nodes_by_type(self, nodetype): 19 nodes = [] 20 if isinstance(self, nodetype): 21 nodes.append(self) 22 nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype)) 23 nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) 24 return nodes 25 26 def check_condition(self, context): 27 return False 28 29 def render(self, context): 30 if self.check_condition(context): 31 return self.nodelist_true.render(context) 32 elif self.nodelist_false: 33 return self.nodelist_false.render(context) 34 return u'' 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(bits, until=()): 48 args = [] 49 kwargs = {} 50 while True: 51 name = bits.pop_name() 52 if name in until: 53 bits.pushback() 54 break 55 if name and bits.pop_lexem('='): 56 kwargs[name] = bits.parse_expression(required=True) 57 else: 58 if name: 59 bits.pushback() 60 try: 61 args.append(bits.parse_expression()) 62 except TokenSyntaxError: 63 break 64 if not bits.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 TokenSyntaxError 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 -
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 class LookupError(Exception): 8 def __init__(self, var, msg, params=()): 9 self.var = var 10 self.msg = msg 11 self.params = params 12 13 def __str__(self): 14 return unicode(self).encode('utf-8') 15 16 def __unicode__(self): 17 return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params]) 18 19 20 class Expression(object): 21 def resolve_safe(self, context, default=None): 22 try: 23 return self.resolve(context) 24 except LookupError: 25 return default 26 27 def resolve(self, context): 28 pass 29 30 class Literal(Expression): 31 def __init__(self, value): 32 self.value = value 33 34 def resolve(self, context): 35 return self.value 36 37 class Lookup(Expression): 38 def __init__(self, lookups, var=None): 39 self.var = var 40 self.lookups = lookups 41 42 def __str__(self): 43 return "%s" % self.var 44 45 def resolve(self, context): 46 current = context 47 for bit in self.lookups: 48 try: # dictionary lookup 49 current = current[bit] 50 except (TypeError, AttributeError, KeyError): 51 try: # attribute lookup 52 current = getattr(current, bit) 53 if callable(current): 54 if getattr(current, 'alters_data', False): 55 current = settings.TEMPLATE_STRING_IF_INVALID 56 else: 57 try: # method call (assuming no args required) 58 current = current() 59 except TypeError: # arguments *were* required 60 # GOTCHA: This will also catch any TypeError 61 # raised in the function itself. 62 current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call 63 except Exception, e: 64 if getattr(e, 'silent_variable_failure', False): 65 current = settings.TEMPLATE_STRING_IF_INVALID 66 else: 67 raise 68 except (TypeError, AttributeError): 69 try: # list-index lookup 70 current = current[int(bit)] 71 except (IndexError, # list index out of range 72 ValueError, # invalid literal for int() 73 KeyError, # current is a dict without `int(bit)` key 74 TypeError, # unsubscriptable object 75 ): 76 raise LookupError(self.var, "Failed lookup for key [%s] in %r", (bit, current)) 77 except Exception, e: 78 if getattr(e, 'silent_variable_failure', False): 79 current = settings.TEMPLATE_STRING_IF_INVALID 80 else: 81 raise 82 83 return current 84 85 class FilterExpression(Expression): 86 def __init__(self, root, filters): 87 self.root = root 88 self.filters = filters 89 90 def resolve(self, context): 91 try: 92 obj = self.root.resolve(context) 93 except LookupError: 94 if not self.filters: 95 raise 96 obj = settings.TEMPLATE_STRING_IF_INVALID 97 for func, args in self.filters: 98 arg_vals = [] 99 for arg in args: 100 arg_vals.append(arg.resolve(context)) 101 if getattr(func, 'needs_autoescape', False): 102 new_obj = func(obj, autoescape=context.autoescape, *arg_vals) 103 else: 104 new_obj = func(obj, *arg_vals) 105 if getattr(func, 'is_safe', False) and isinstance(obj, SafeData): 106 obj = mark_safe(new_obj) 107 elif isinstance(obj, EscapeData): 108 obj = mark_for_escaping(new_obj) 109 else: 110 obj = new_obj 111 return obj 112 113 def __str__(self): 114 return str(self.root)+'|<filtered>' -
django/template/defaulttags.py
8 8 except NameError: 9 9 from django.utils.itercompat import reversed # Python 2.3 fallback 10 10 11 from django.template import Node, NodeList, Template, Context , Variable12 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_END11 from django.template import Node, NodeList, Template, Context 12 from django.template import TemplateSyntaxError, LookupError, TokenSyntaxError 13 13 from django.template import get_library, Library, InvalidTemplateLibrary 14 from django.template.compiler import uses_token_stream, 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 '' 93 89 return "<For Node: for %s in %s, tail_len: %d%s>" % \ 94 (', '.join(self.loopvars), self.sequence, len(self.nodelist _loop),90 (', '.join(self.loopvars), self.sequence, len(self.nodelist), 95 91 reversed_text) 96 92 97 93 def __iter__(self): 98 for node in self.nodelist _loop:94 for node in self.nodelist: 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 class IfChangedNode( Node):160 def __init__(self, nodelist_true, nodelist_false, *varlist):161 s elf.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false162 self. _last_seen = None163 self. _varlist = map(Variable, varlist)164 self. _id = str(id(self))145 class IfChangedNode(ConditionalNode): 146 def __init__(self, nodelist_true, nodelist_false, vallist): 147 super(IfChangedNode, self).__init__(nodelist_true, nodelist_false) 148 self.last_seen = None 149 self.vallist = vallist 150 self.id = str(id(self)) 165 151 166 152 def render(self, context): 167 if 'forloop' in context and self. _id not in context['forloop']:168 self. _last_seen = None169 context['forloop'][self. _id] = 1153 if 'forloop' in context and self.id not in context['forloop']: 154 self.last_seen = None 155 context['forloop'][self.id] = 1 170 156 try: 171 if self. _varlist: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._varlist]160 compare_to = [expr.resolve(context) for expr in self.vallist] 175 161 else: 176 162 compare_to = self.nodelist_true.render(context) 177 except VariableDoesNotExist:163 except LookupError: 178 164 compare_to = None 179 165 180 if compare_to != self._last_seen:181 firstloop = (self. _last_seen == None)182 self. _last_seen = compare_to166 if compare_to != self.last_seen: 167 firstloop = (self.last_seen == None) 168 self.last_seen = compare_to 183 169 context.push() 184 170 context['ifchanged'] = {'firstloop': firstloop} 185 171 content = self.nodelist_true.render(context) … … 187 173 return content 188 174 elif self.nodelist_false: 189 175 return self.nodelist_false.render(context) 190 return ''176 return u'' 191 177 192 class IfEqualNode(Node): 193 def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): 194 self.var1, self.var2 = Variable(var1), Variable(var2) 195 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 178 179 class IfEqualNode(ConditionalNode): 180 def __init__(self, val1, val2, nodelist_true, nodelist_false, negate): 181 super(IfEqualNode, self).__init__(nodelist_true, nodelist_false) 182 self.val1, self.val2 = val1, val2 196 183 self.negate = negate 197 184 198 def __repr__(self): 199 return "<IfEqualNode>" 185 def check_condition(self, context): 186 val1, val2 = self.val1.resolve_safe(context), self.val2.resolve_safe(context) 187 return self.negate == (val1 != val2) 200 188 201 def render(self, context):202 try:203 val1 = self.var1.resolve(context)204 except VariableDoesNotExist:205 val1 = None206 try:207 val2 = self.var2.resolve(context)208 except VariableDoesNotExist:209 val2 = None210 if (self.negate and val1 != val2) or (not self.negate and val1 == val2):211 return self.nodelist_true.render(context)212 return self.nodelist_false.render(context)213 189 214 class IfNode( Node):190 class IfNode(ConditionalNode): 215 191 def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type): 216 self.bool_exprs = bool_exprs 217 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 218 self.link_type = link_type 192 super(IfNode, self).__init__(nodelist_true, nodelist_false) 193 self.bool_exprs, self.link_type = bool_exprs, link_type 219 194 220 def __repr__(self): 221 return "<If node>" 222 223 def __iter__(self): 224 for node in self.nodelist_true: 225 yield node 226 for node in self.nodelist_false: 227 yield node 228 229 def get_nodes_by_type(self, nodetype): 230 nodes = [] 231 if isinstance(self, nodetype): 232 nodes.append(self) 233 nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype)) 234 nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) 235 return nodes 236 237 def render(self, context): 238 if self.link_type == IfNode.LinkTypes.or_: 239 for ifnot, bool_expr in self.bool_exprs: 240 try: 241 value = bool_expr.resolve(context, True) 242 except VariableDoesNotExist: 243 value = None 244 if (value and not ifnot) or (ifnot and not value): 245 return self.nodelist_true.render(context) 246 return self.nodelist_false.render(context) 195 def check_condition(self, context): 196 if self.link_type == 'or': 197 for negated, bool_expr in self.bool_exprs: 198 value = bool_expr.resolve_safe(context, default=False) 199 if bool(value) != negated: 200 return True 201 return False 247 202 else: 248 for ifnot, bool_expr in self.bool_exprs: 249 try: 250 value = bool_expr.resolve(context, True) 251 except VariableDoesNotExist: 252 value = None 253 if not ((value and not ifnot) or (ifnot and not value)): 254 return self.nodelist_false.render(context) 255 return self.nodelist_true.render(context) 203 for negated, bool_expr in self.bool_exprs: 204 value = bool_expr.resolve_safe(context, default=False) 205 if bool(value) == negated: 206 return False 207 return True 208 256 209 257 class LinkTypes:258 and_ = 0,259 or_ = 1260 261 210 class RegroupNode(Node): 262 211 def __init__(self, target, expression, var_name): 263 212 self.target, self.expression = target, expression 264 213 self.var_name = var_name 265 214 266 215 def render(self, context): 267 obj_list = self.target.resolve (context, True)216 obj_list = self.target.resolve_safe(context) 268 217 if obj_list == None: 269 218 # target variable wasn't found in context; fail silently. 270 219 context[self.var_name] = [] 271 return ''220 return u'' 272 221 # List of dictionaries in the format: 273 222 # {'grouper': 'key', 'list': [list of contents]}. 274 223 context[self.var_name] = [ 275 224 {'grouper': key, 'list': list(val)} 276 225 for key, val in 277 groupby(obj_list, lambda v, f=self.expression.resolve : f(v, True))226 groupby(obj_list, lambda v, f=self.expression.resolve_safe: f(v)) 278 227 ] 279 return ''228 return u'' 280 229 281 230 def include_is_allowed(filepath): 282 231 for root in settings.ALLOWED_INCLUDE_ROOTS: … … 293 242 if settings.DEBUG: 294 243 return "[Didn't have permission to include file]" 295 244 else: 296 return '' # Fail silently for invalid includes.245 return u'' # Fail silently for invalid includes. 297 246 try: 298 247 fp = open(self.filepath, 'r') 299 248 output = fp.read() … … 308 257 if settings.DEBUG: 309 258 return "[Included template had syntax error: %s]" % e 310 259 else: 311 return '' # Fail silently for invalid included templates.260 return u'' # Fail silently for invalid included templates. 312 261 return output 313 262 314 class LoadNode(Node): 315 def render(self, context): 316 return '' 263 class LoadNode(EmptyNode): pass 317 264 318 265 class NowNode(Node): 319 266 def __init__(self, format_string): … … 323 270 from datetime import datetime 324 271 from django.utils.dateformat import DateFormat 325 272 df = DateFormat(datetime.now()) 326 return df.format(self.format_string )273 return df.format(self.format_string.resolve_safe(context)) 327 274 328 275 class SpacelessNode(Node): 329 276 def __init__(self, nodelist): … … 359 306 360 307 def render(self, context): 361 308 from django.core.urlresolvers import reverse, NoReverseMatch 362 args = [arg.resolve (context) for arg in self.args]363 kwargs = dict([(smart_str(k,'ascii'), v.resolve (context))309 args = [arg.resolve_safe(context) for arg in self.args] 310 kwargs = dict([(smart_str(k,'ascii'), v.resolve_safe(context)) 364 311 for k, v in self.kwargs.items()]) 365 312 366 313 … … 382 329 383 330 if self.asvar: 384 331 context[self.asvar] = url 385 return ''332 return u'' 386 333 else: 387 334 return url 388 335 … … 396 343 try: 397 344 value = self.val_expr.resolve(context) 398 345 maxvalue = self.max_expr.resolve(context) 399 except VariableDoesNotExist:400 return ''346 except LookupError: 347 return u'' 401 348 try: 402 value = float(value) 403 maxvalue = float(maxvalue) 404 ratio = (value / maxvalue) * int(self.max_width) 349 ratio = (float(value) / float(maxvalue)) * int(self.max_width) 405 350 except (ValueError, ZeroDivisionError): 406 return ''407 return str(int(round(ratio)))351 return u'' 352 return unicode(int(round(ratio))) 408 353 409 354 class WithNode(Node): 410 def __init__(self, var, name, nodelist):411 self. var = var412 self.name = name 355 def __init__(self, expr, name, nodelist): 356 self.expr = expr 357 self.name = name 413 358 self.nodelist = nodelist 414 359 415 def __repr__(self):416 return "<WithNode>"417 418 360 def render(self, context): 419 val = self.var.resolve(context)420 361 context.push() 421 context[self.name] = val362 context[self.name] = self.expr.resolve_safe(context) 422 363 output = self.nodelist.render(context) 423 364 context.pop() 424 365 return output … … 434 375 arg = args[1] 435 376 if arg not in (u'on', u'off'): 436 377 raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'") 437 nodelist = parser.parse(('endautoescape',)) 438 parser.delete_first_token() 378 nodelist = parser.parse_nodelist(('endautoescape',)) 439 379 return AutoEscapeControlNode((arg == 'on'), nodelist) 440 380 autoescape = register.tag(autoescape) 441 381 … … 493 433 # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %} 494 434 # case. 495 435 args[1:2] = ['"%s"' % arg for arg in args[1].split(",")] 496 436 497 437 if len(args) == 2: 498 438 # {% cycle foo %} case. 499 439 name = args[1] … … 505 445 506 446 if len(args) > 4 and args[-2] == 'as': 507 447 name = args[-1] 508 node = CycleNode(args[1:-2], name) 448 values = [parser.compile_filter(arg) for arg in args[1:-2]] 449 node = CycleNode(values, name) 509 450 if not hasattr(parser, '_namedCycleNodes'): 510 451 parser._namedCycleNodes = {} 511 452 parser._namedCycleNodes[name] = node 512 453 else: 513 node = CycleNode(args[1:]) 454 values = [parser.compile_filter(arg) for arg in args[1:]] 455 node = CycleNode(values) 514 456 return node 515 457 cycle = register.tag(cycle) 516 458 … … 542 484 This text will be HTML-escaped, and will appear in lowercase. 543 485 {% endfilter %} 544 486 """ 545 _, rest = token.contents.split(None, 1) 546 filter_expr = parser.compile_filter("var|%s" % (rest)) 547 for func, unused in filter_expr.filters: 487 name, filter_ = token.contents.split(None, 1) 488 bits = parser.token_stream("var|%s" % filter_) 489 try: 490 filter_expr = bits.parse_expression() 491 except TokenSyntaxError: 492 raise TemplateSyntaxError("'%s' requires a valid filter chain, got: '%s'" % name, filter_) 493 for func, args in filter_expr.filters: 548 494 if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'): 549 495 raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) 550 nodelist = parser.parse(('endfilter',)) 551 parser.delete_first_token() 496 nodelist = parser.parse_nodelist(('endfilter',)) 552 497 return FilterNode(filter_expr, nodelist) 553 498 do_filter = register.tag("filter", do_filter) 554 499 555 500 #@register.tag 556 def firstof(parser, token): 501 #@uses_token_stream 502 def firstof(parser, bits): 557 503 """ 558 504 Outputs the first variable passed that is not False. 559 505 … … 581 527 {% firstof var1 var2 var3 "fallback value" %} 582 528 583 529 """ 584 bits = token.split_contents()[1:] 585 if len(bits) < 1: 586 raise TemplateSyntaxError("'firstof' statement requires at least one" 587 " argument") 588 return FirstOfNode(bits) 589 firstof = register.tag(firstof) 530 expressions = bits.parse_expression_list(minimum=1) 531 return FirstOfNode(expressions) 532 firstof = register.tag(uses_token_stream(firstof)) 590 533 591 534 #@register.tag(name="for") 592 def do_for(parser, token): 535 #@uses_token_stream 536 def do_for(parser, bits): 593 537 """ 594 538 Loops over each item in an array. 595 539 … … 628 572 ========================== ================================================ 629 573 630 574 """ 631 bits = token.contents.split() 632 if len(bits) < 4: 633 raise TemplateSyntaxError("'for' statements should have at least four" 634 " words: %s" % token.contents) 575 loopvars = [] 576 while True: 577 var = bits.pop_name() 578 if not var: 579 break 580 loopvars.append(var) 581 if not bits.pop_lexem(','): 582 break 583 if not loopvars: 584 raise TemplateSyntaxError("'for' tag requires at least one loopvar") 585 586 if not bits.pop_lexem('in'): 587 raise TemplateSyntaxError("'for' tag requires 'in' keyword") 588 sequence = bits.parse_expression(required=True) 589 reversed = bits.pop_lexem('reversed') 590 nodelist = parser.parse_nodelist(('endfor',)) 591 return ForNode(loopvars, sequence, reversed, nodelist) 592 do_for = register.tag("for", uses_token_stream(do_for)) 635 593 636 is_reversed = bits[-1] == 'reversed' 637 in_index = is_reversed and -3 or -2 638 if bits[in_index] != 'in': 639 raise TemplateSyntaxError("'for' statements should use the format" 640 " 'for x in y': %s" % token.contents) 594 def do_ifequal(parser, bits, negate): 595 val1, val2 = bits.parse_expression_list(count=2) 596 nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name) 597 return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate) 641 598 642 loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')643 for var in loopvars:644 if not var or ' ' in var:645 raise TemplateSyntaxError("'for' tag received an invalid argument:"646 " %s" % token.contents)647 648 sequence = parser.compile_filter(bits[in_index+1])649 nodelist_loop = parser.parse(('endfor',))650 parser.delete_first_token()651 return ForNode(loopvars, sequence, is_reversed, nodelist_loop)652 do_for = register.tag("for", do_for)653 654 def do_ifequal(parser, token, negate):655 bits = list(token.split_contents())656 if len(bits) != 3:657 raise TemplateSyntaxError, "%r takes two arguments" % bits[0]658 end_tag = 'end' + bits[0]659 nodelist_true = parser.parse(('else', end_tag))660 token = parser.next_token()661 if token.contents == 'else':662 nodelist_false = parser.parse((end_tag,))663 parser.delete_first_token()664 else:665 nodelist_false = NodeList()666 return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)667 668 599 #@register.tag 669 def ifequal(parser, token): 600 #@uses_token_stream 601 def ifequal(parser, bits): 670 602 """ 671 603 Outputs the contents of the block if the two arguments equal each other. 672 604 … … 682 614 ... 683 615 {% endifnotequal %} 684 616 """ 685 return do_ifequal(parser, token, False)686 ifequal = register.tag( ifequal)617 return do_ifequal(parser, bits, False) 618 ifequal = register.tag(uses_token_stream(ifequal)) 687 619 688 620 #@register.tag 689 def ifnotequal(parser, token): 621 #@uses_token_stream 622 def ifnotequal(parser, bits): 690 623 """ 691 624 Outputs the contents of the block if the two arguments are not equal. 692 625 See ifequal. 693 626 """ 694 return do_ifequal(parser, token, True)695 ifnotequal = register.tag( ifnotequal)627 return do_ifequal(parser, bits, True) 628 ifnotequal = register.tag(uses_token_stream(ifnotequal)) 696 629 697 630 #@register.tag(name="if") 698 def do_if(parser, token): 631 #@uses_token_stream 632 def do_if(parser, bits): 699 633 """ 700 634 The ``{% if %}`` tag evaluates a variable, and if that variable is "true" 701 635 (i.e., exists, is not empty, and is not a false boolean value), the … … 753 687 {% endif %} 754 688 {% endif %} 755 689 """ 756 bits = token.contents.split() 757 del bits[0] 758 if not bits: 759 raise TemplateSyntaxError("'if' statement requires at least one argument") 760 # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d'] 761 bitstr = ' '.join(bits) 762 boolpairs = bitstr.split(' and ') 690 link_type = None 691 link = None 763 692 boolvars = [] 764 if len(boolpairs) == 1: 765 link_type = IfNode.LinkTypes.or_ 766 boolpairs = bitstr.split(' or ') 767 else: 768 link_type = IfNode.LinkTypes.and_ 769 if ' or ' in bitstr: 770 raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'" 771 for boolpair in boolpairs: 772 if ' ' in boolpair: 773 try: 774 not_, boolvar = boolpair.split() 775 except ValueError: 776 raise TemplateSyntaxError, "'if' statement improperly formatted" 777 if not_ != 'not': 778 raise TemplateSyntaxError, "Expected 'not' in if statement" 779 boolvars.append((True, parser.compile_filter(boolvar))) 693 while True: 694 negated = False 695 if bits.pop_lexem('not'): 696 negated = True 697 expr = bits.parse_expression(required=True) 698 boolvars.append((negated, expr)) 699 link = bits.pop_name() 700 if not link: 701 break 702 if link_type: 703 if link_type != link: 704 bits.syntax_error("can't mix 'and' and 'or'") 780 705 else: 781 boolvars.append((False, parser.compile_filter(boolpair))) 782 nodelist_true = parser.parse(('else', 'endif')) 783 token = parser.next_token() 784 if token.contents == 'else': 785 nodelist_false = parser.parse(('endif',)) 786 parser.delete_first_token() 787 else: 788 nodelist_false = NodeList() 706 if not link in ('and', 'or'): 707 bits.pushback() 708 bits.expected("'and' or 'or'") 709 link_type = link 710 711 nodelist_true, nodelist_false = parse_conditional_nodelists(parser, 'if') 789 712 return IfNode(boolvars, nodelist_true, nodelist_false, link_type) 790 do_if = register.tag("if", do_if)713 do_if = register.tag("if", uses_token_stream(do_if)) 791 714 792 715 #@register.tag 793 def ifchanged(parser, token): 716 #@uses_token_stream 717 def ifchanged(parser, bits): 794 718 """ 795 719 Checks if a value has changed from the last iteration of a loop. 796 720 … … 817 741 {{ date.hour }} 818 742 {% endifchanged %} 819 743 {% endfor %} 820 """ 821 bits = token.contents.split() 822 nodelist_true = parser.parse(('else', 'endifchanged')) 823 token = parser.next_token() 824 if token.contents == 'else': 825 nodelist_false = parser.parse(('endifchanged',)) 826 parser.delete_first_token() 827 else: 828 nodelist_false = NodeList() 829 return IfChangedNode(nodelist_true, nodelist_false, *bits[1:]) 830 ifchanged = register.tag(ifchanged) 744 """ 745 values = bits.parse_expression_list() 746 nodelist_true, nodelist_false = parse_conditional_nodelists(parser, bits.name) 747 return IfChangedNode(nodelist_true, nodelist_false, values) 748 ifchanged = register.tag(uses_token_stream(ifchanged)) 831 749 832 750 #@register.tag 833 751 def ssi(parser, token): … … 882 800 load = register.tag(load) 883 801 884 802 #@register.tag 885 def now(parser, token): 803 #@uses_token_stream 804 def now(parser, bits): 886 805 """ 887 806 Displays the date, formatted according to the given string. 888 807 … … 893 812 894 813 It is {% now "jS F Y H:i" %} 895 814 """ 896 bits = token.contents.split('"') 897 if len(bits) != 3: 898 raise TemplateSyntaxError, "'now' statement takes one argument" 899 format_string = bits[1] 815 format_string = bits.parse_expression(required=True) 900 816 return NowNode(format_string) 901 now = register.tag( now)817 now = register.tag(uses_token_stream(now)) 902 818 903 819 #@register.tag 904 def regroup(parser, token): 820 #@uses_token_stream 821 def regroup(parser, bits): 905 822 """ 906 823 Regroups a list of alike objects by a common attribute. 907 824 … … 947 864 {% regroup people|dictsort:"gender" by gender as grouped %} 948 865 949 866 """ 950 firstbits = token.contents.split(None, 3) 951 if len(firstbits) != 4: 952 raise TemplateSyntaxError, "'regroup' tag takes five arguments" 953 target = parser.compile_filter(firstbits[1]) 954 if firstbits[2] != 'by': 955 raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") 956 lastbits_reversed = firstbits[3][::-1].split(None, 2) 957 if lastbits_reversed[1][::-1] != 'as': 958 raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" 959 " be 'as'") 960 961 expression = parser.compile_filter(lastbits_reversed[2][::-1]) 962 963 var_name = lastbits_reversed[0][::-1] 867 target = bits.parse_expression(required=True) 868 if not bits.pop_lexem('by'): 869 raise bits.expected("'by'") 870 expression = bits.parse_expression(required=True) 871 try: 872 var_name = parse_as(bits) 873 except TokenSyntaxError: 874 raise bits.expected("as <name>") 964 875 return RegroupNode(target, expression, var_name) 965 regroup = register.tag( regroup)876 regroup = register.tag(uses_token_stream(regroup)) 966 877 967 878 def spaceless(parser, token): 968 879 """ … … 989 900 </strong> 990 901 {% endspaceless %} 991 902 """ 992 nodelist = parser.parse(('endspaceless',)) 993 parser.delete_first_token() 994 return SpacelessNode(nodelist) 903 return SpacelessNode(parser.parse_nodelist(('endspaceless',))) 995 904 spaceless = register.tag(spaceless) 996 905 997 906 #@register.tag … … 1028 937 return TemplateTagNode(tag) 1029 938 templatetag = register.tag(templatetag) 1030 939 1031 def url(parser, token): 940 #@register.tag 941 #@uses_token_stream 942 def url(parser, bits): 1032 943 """ 1033 944 Returns an absolute URL matching given view with its parameters. 1034 945 … … 1059 970 1060 971 The URL will look like ``/clients/client/123/``. 1061 972 """ 1062 bits = token.contents.split(' ') 1063 if len(bits) < 2: 1064 raise TemplateSyntaxError("'%s' takes at least one argument" 1065 " (path to a view)" % bits[0]) 1066 viewname = bits[1] 1067 args = [] 1068 kwargs = {} 1069 asvar = None 1070 1071 if len(bits) > 2: 1072 bits = iter(bits[2:]) 1073 for bit in bits: 1074 if bit == 'as': 1075 asvar = bits.next() 1076 break 1077 else: 1078 for arg in bit.split(","): 1079 if '=' in arg: 1080 k, v = arg.split('=', 1) 1081 k = k.strip() 1082 kwargs[k] = parser.compile_filter(v) 1083 elif arg: 1084 args.append(parser.compile_filter(arg)) 1085 return URLNode(viewname, args, kwargs, asvar) 1086 url = register.tag(url) 973 view = bits.parse_string(bare=True, required=True) 974 args, kwargs = parse_args_and_kwargs(bits, until=('as',)) 975 try: 976 asvar = parse_as(bits) 977 except TokenSyntaxError: 978 asvar = None 979 return URLNode(view, args, kwargs, asvar) 980 url = register.tag(uses_token_stream(url)) 1087 981 1088 982 #@register.tag 1089 def widthratio(parser, token): 983 #@uses_token_stream 984 def widthratio(parser, bits): 1090 985 """ 1091 986 For creating bar charts and such, this tag calculates the ratio of a given 1092 987 value to a maximum value, and then applies that ratio to a constant. … … 1099 994 the above example will be 88 pixels wide (because 175/200 = .875; 1100 995 .875 * 100 = 87.5 which is rounded up to 88). 1101 996 """ 1102 bits = token.contents.split() 1103 if len(bits) != 4: 1104 raise TemplateSyntaxError("widthratio takes three arguments") 1105 tag, this_value_expr, max_value_expr, max_width = bits 1106 try: 1107 max_width = int(max_width) 1108 except ValueError: 1109 raise TemplateSyntaxError("widthratio final argument must be an integer") 1110 return WidthRatioNode(parser.compile_filter(this_value_expr), 1111 parser.compile_filter(max_value_expr), max_width) 1112 widthratio = register.tag(widthratio) 997 this_value_expr, max_value_expr = bits.parse_expression_list(count=2) 998 max_width = bits.parse_int(required=True) 999 return WidthRatioNode(this_value_expr, max_value_expr, max_width) 1000 widthratio = register.tag(uses_token_stream(widthratio)) 1113 1001 1114 1002 #@register.tag 1115 def do_with(parser, token): 1003 #@uses_token_stream 1004 def do_with(parser, bits): 1116 1005 """ 1117 1006 Adds a value to the context (inside of this block) for caching and easy 1118 1007 access. … … 1122 1011 {% with person.some_sql_method as total %} 1123 1012 {{ total }} object{{ total|pluralize }} 1124 1013 {% endwith %} 1014 1125 1015 """ 1126 bits = list(token.split_contents()) 1127 if len(bits) != 4 or bits[2] != "as": 1128 raise TemplateSyntaxError("%r expected format is 'value as name'" % 1129 bits[0]) 1130 var = parser.compile_filter(bits[1]) 1131 name = bits[3] 1132 nodelist = parser.parse(('endwith',)) 1133 parser.delete_first_token() 1134 return WithNode(var, name, nodelist) 1135 do_with = register.tag('with', do_with) 1016 try: 1017 expr = bits.parse_expression() 1018 name = parse_as(bits) 1019 except TokenSyntaxError: 1020 bits.expected("value as name") 1021 nodelist = parser.parse_nodelist(('endwith',)) 1022 return WithNode(expr, name, nodelist) 1023 do_with = register.tag('with', uses_token_stream(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, Variable 2 from django.template import Library, Node, TextNode 3 from django.template.loader import get_template, get_template_from_string, find_template_source 1 from django.template import TemplateSyntaxError, Library, Node, TextNode 2 from django.template.compiler import uses_token_stream 3 from django.template.loader import TemplateDoesNotExist, get_template, get_template_from_string, find_template_source 4 from django.template.expressions import Literal 4 5 from django.conf import settings 5 6 from django.utils.safestring import mark_safe 6 7 … … 28 29 def super(self): 29 30 if self.parent: 30 31 return mark_safe(self.parent.render(self.context)) 31 return ''32 return u'' 32 33 33 34 def add_parent(self, nodelist): 34 35 if self.parent: … … 39 40 class ExtendsNode(Node): 40 41 must_be_first = True 41 42 42 def __init__(self, nodelist, parent_name, parent_name_expr,template_dirs=None):43 def __init__(self, nodelist, parent_name, template_dirs=None): 43 44 self.nodelist = nodelist 44 self.parent_name , self.parent_name_expr = parent_name, parent_name_expr45 self.parent_name = parent_name 45 46 self.template_dirs = template_dirs 46 47 47 48 def __repr__(self): 48 if self.parent_name_expr:49 return "<ExtendsNode: extends %s>" % self.parent_name_expr.token50 49 return '<ExtendsNode: extends "%s">' % self.parent_name 51 50 52 51 def get_parent(self, context): 53 if self.parent_name_expr: 54 self.parent_name = self.parent_name_expr.resolve(context) 55 parent = self.parent_name 52 parent = self.parent_name.resolve_safe(context) 56 53 if not parent: 57 54 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 55 raise TemplateSyntaxError, error_msg 61 56 if hasattr(parent, 'render'): 62 57 return parent # parent is a Template object … … 114 109 115 110 class IncludeNode(Node): 116 111 def __init__(self, template_name): 117 self.template_name = Variable(template_name)112 self.template_name = template_name 118 113 119 114 def render(self, context): 120 115 try: 121 template_name = self.template_name.resolve (context)116 template_name = self.template_name.resolve_safe(context) 122 117 t = get_template(template_name) 123 118 return t.render(context) 124 119 except TemplateSyntaxError, e: 125 120 if settings.TEMPLATE_DEBUG: 126 121 raise 127 return ''122 return u'' 128 123 except: 129 return '' # Fail silently for invalid included templates.124 return u'' # Fail silently for invalid included templates. 130 125 126 127 #@register.tag('block') 131 128 def do_block(parser, token): 132 129 """ 133 130 Define a block that can be overridden by child templates. … … 144 141 parser.__loaded_blocks.append(block_name) 145 142 except AttributeError: # parser.__loaded_blocks isn't a list yet 146 143 parser.__loaded_blocks = [block_name] 147 nodelist = parser.parse(('endblock', 'endblock %s' % block_name)) 148 parser.delete_first_token() 144 nodelist = parser.parse_nodelist(('endblock', 'endblock %s' % block_name)) 149 145 return BlockNode(block_name, nodelist) 146 register.tag('block', do_block) 150 147 151 def do_extends(parser, token): 148 149 #@register.tag('extends') 150 #@uses_token_stream 151 def do_extends(parser, bits): 152 152 """ 153 153 Signal that this template extends a parent template. 154 154 155 155 This tag may be used in two ways: ``{% extends "base" %}`` (with quotes) 156 156 uses the literal value "base" as the name of the parent template to extend, 157 or ``{% extends variable %}`` uses the value of ``variable`` as either the157 or ``{% extends expression %}`` uses the value of ``expression`` as either the 158 158 name of the parent template to extend (if it evaluates to a string) or as 159 159 the parent tempate itelf (if it evaluates to a Template object). 160 160 """ 161 bits = token.contents.split() 162 if len(bits) != 2: 163 raise TemplateSyntaxError, "'%s' takes one argument" % bits[0] 164 parent_name, parent_name_expr = None, None 165 if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]: 166 parent_name = bits[1][1:-1] 167 else: 168 parent_name_expr = parser.compile_filter(bits[1]) 161 parent_name = bits.parse_expression(required=True) 169 162 nodelist = parser.parse() 170 163 if nodelist.get_nodes_by_type(ExtendsNode): 171 raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits[0] 172 return ExtendsNode(nodelist, parent_name, parent_name_expr) 164 raise TemplateSyntaxError, "'%s' cannot appear more than once in the same template" % bits.name 165 return ExtendsNode(nodelist, parent_name) 166 register.tag('extends', uses_token_stream(do_extends)) 173 167 174 def do_include(parser, token): 168 169 #@register.tag('include') 170 #@uses_token_stream 171 def do_include(parser, bits): 175 172 """ 176 173 Loads a template and renders it with the current context. 177 174 … … 179 176 180 177 {% include "foo/some_include" %} 181 178 """ 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]) 189 190 register.tag('block', do_block) 191 register.tag('extends', do_extends) 192 register.tag('include', do_include) 179 template_name = bits.parse_expression(required=True) 180 if isinstance(template_name, Literal): 181 # remove ConstantIncludeNode and this hack will be gone 182 return ConstantIncludeNode(template_name.resolve(None)) 183 return IncludeNode(template_name) 184 register.tag('include', uses_token_stream(do_include)) -
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 2 from django.utils.encoding import force_unicode 3 from django.utils.html import escape 4 from django.utils.safestring import SafeData, EscapeData 1 from django.template.compiler import Lexer, Parser, Token, TokenStream, tag_re, bit_re 2 from django.template import NodeList, ExpressionNode, TemplateSyntaxError 3 from django.utils.encoding import force_unicode, smart_str 5 4 6 5 class DebugLexer(Lexer): 7 6 def __init__(self, template_string, origin): … … 50 49 return DebugNodeList() 51 50 52 51 def create_variable_node(self, contents): 53 return Debug VariableNode(contents)52 return DebugExpressionNode(contents) 54 53 55 54 def extend_nodelist(self, nodelist, node, token): 56 55 node.source = token.source … … 65 64 if not hasattr(e, 'source'): 66 65 e.source = token.source 67 66 67 def token_stream(self, token): 68 return DebugTokenStream(self, token) 69 70 68 71 class DebugNodeList(NodeList): 69 72 def render_node(self, node, context): 70 73 try: … … 81 84 raise wrapped 82 85 return result 83 86 84 class Debug VariableNode(VariableNode):87 class DebugExpressionNode(ExpressionNode): 85 88 def render(self, context): 86 89 try: 87 output = force_unicode(self.filter_expression.resolve(context))90 return super(DebugExpressionNode, self).render(context) 88 91 except TemplateSyntaxError, e: 89 92 if not hasattr(e, 'source'): 90 93 e.source = self.source 91 94 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 95 96 class DebugTokenStream(TokenStream): 97 def syntax_error(self, msg): 98 if self.name: 99 msg = u"{%% %s %%} %s" % (self.name, msg) 100 if self.token: 101 raise self.parser.source_error(self.token.source, msg) 102 raise TemplateSyntaxError(msg) 103 -
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) -
tests/regressiontests/templates/tests.py
132 132 133 133 def test_token_smart_split(self): 134 134 # Regression test for #7027 135 token = template. Token(template.TOKEN_BLOCK, 'sometag _("Page not found") value|yesno:_("yes,no")')135 token = template.compiler.Token(template.compiler.TOKEN_BLOCK, 'sometag _("Page not found") value|yesno:_("yes,no")') 136 136 split = token.split_contents() 137 137 self.assertEqual(split, ["sometag", '_("Page not found")', 'value|yesno:_("yes,no")']) 138 138 … … 194 194 output = self.render(test_template, vals) 195 195 except Exception, e: 196 196 if e.__class__ != result: 197 raise197 #raise 198 198 failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e)) 199 199 continue 200 200 if output != result: … … 288 288 'basic-syntax25': ('{{ "fred" }}', {}, "fred"), 289 289 'basic-syntax26': (r'{{ "\"fred\"" }}', {}, "\"fred\""), 290 290 'basic-syntax27': (r'{{ _("\"fred\"") }}', {}, "\"fred\""), 291 'basic-syntax28': ("{{ 'fred' }}", {}, "fred"), 292 'basic-syntax29': (r"{{ '\'fred\'' }}", {}, "'fred'"), 293 'basic-syntax30': (r"{{ _('\'fred\'') }}", {}, "'fred'"), 291 294 292 295 # List-index syntax allows a template to access a certain item of a subscriptable object. 293 296 'list-index01': ("{{ var.1 }}", {"var": ["first item", "second item"]}, "second item"), … … 347 350 'filter-syntax11': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'), 348 351 349 352 # Default argument testing 350 'filter-syntax12': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'), 353 'filter-syntax12': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'), 351 354 352 355 # Fail silently for methods that raise an exception with a 353 356 # "silent_variable_failure" attribute … … 375 378 376 379 #filters should accept empty string constants 377 380 'filter-syntax20': ('{{ ""|default_if_none:"was none" }}', {}, ""), 381 382 # Single-quoted argument 383 'filter-syntax20': (r"{{ var|yesno:'yup,nup,mup' }} {{ var|yesno }}", {"var": True}, 'yup yes'), 378 384 379 385 ### COMMENT SYNTAX ######################################################## 380 386 'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"), … … 419 425 'cycle14': ("{% cycle one two as foo %}{% cycle foo %}", {'one': '1','two': '2'}, '12'), 420 426 'cycle13': ("{% for i in test %}{% cycle aye bee %}{{ i }},{% endfor %}", {'test': range(5), 'aye': 'a', 'bee': 'b'}, 'a0,b1,a2,b3,a4,'), 421 427 428 ### EMPTY STRINGS ######################################################### 429 'emptystring01': ("{{ '' }}", {}, ""), 430 'emptystring02': ("{% ifequal foo '' %}x{% endifequal %}", {'foo': ''}, 'x'), 431 'emptystring03': ("{% ifequal foo|default:'' foo %}x{% endifequal %}", {'foo': ''}, 'x'), 432 422 433 ### EXCEPTIONS ############################################################ 423 434 424 435 # Raise exception for invalid template name … … 621 632 'ifequal-numeric10': ('{% ifequal x -5 %}yes{% endifequal %}', {'x': -5}, 'yes'), 622 633 'ifequal-numeric11': ('{% ifequal x -5.2 %}yes{% endifequal %}', {'x': -5.2}, 'yes'), 623 634 'ifequal-numeric12': ('{% ifequal x +5 %}yes{% endifequal %}', {'x': 5}, 'yes'), 635 636 # FILTER EXPRESSIONS AS ARGUMENTS 637 'ifequal-filter01': ('{% ifequal a|upper "A" %}x{% endifequal %}', {'a': 'a'}, 'x'), 638 'ifequal-filter02': ('{% ifequal "A" a|upper %}x{% endifequal %}', {'a': 'a'}, 'x'), 639 'ifequal-filter03': ('{% ifequal a|upper b|upper %}x{% endifequal %}', {'a': 'x', 'b': 'X'}, 'x'), 640 'ifequal-filter04': ('{% ifequal x|slice:"1" "a" %}x{% endifequal %}', {'x': 'aaa'}, 'x'), 641 'ifequal-filter05': ('{% ifequal x|slice:"1"|upper "A" %}x{% endifequal %}', {'x': 'aaa'}, 'x'), 624 642 625 643 ### IFNOTEQUAL TAG ######################################################## 626 644 'ifnotequal01': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 2}, "yes"), … … 816 834 you 817 835 gentlemen. 818 836 """), 837 838 ### NEGATIVE NUMERIC LITERALS ############################################# 839 'negative-numeric-literal01': ('{{ -1 }}', {}, '-1'), 840 'negative-numeric-literal02': ('{{ -2.01 }}', {}, '-2.01'), 841 'negative-numeric-literal03': ('{{ -0.1 }}', {}, '-0.1'), 842 'negative-numeric-literal04': ('{% ifequal -1 -1 %}x{% endifequal %}', {}, 'x'), 843 'negative-numeric-literal05': ('{{ foo|default:-1 }}', {'foo': None}, '-1'), 819 844 820 845 ### REGROUP TAG ########################################################### 821 846 'regroup01': ('{% regroup data by bar as grouped %}' + \ … … 841 866 '{% endfor %},' + \ 842 867 '{% endfor %}', 843 868 {}, ''), 869 870 'regroup03': ('{% regroup data by created|date:"F Y" as grouped %}' + \ 871 '{% for group in grouped %}' + \ 872 '{{ group.grouper }}' + \ 873 '({% for item in group.list %}' + \ 874 '{{ item.created|date:"d" }}' + \ 875 '{% endfor %})' + \ 876 '{% endfor %}', 877 {'data': [ 878 {'created': datetime(2008, 1, 1)}, 879 {'created': datetime(2008, 2, 2)}, 880 {'created': datetime(2008, 3, 3)}, 881 {'created': datetime(2008, 4, 4)}, 882 ]}, 'January 2008(01)February 2008(02)March 2008(03)April 2008(04)'), 844 883 845 884 ### TEMPLATETAG TAG ####################################################### 846 885 'templatetag01': ('{% templatetag openblock %}', {}, '{%'), … … 902 941 'url07': (u'{% url regressiontests.templates.views.client2 tag=v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), 903 942 'url08': (u'{% url метка_оператора v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), 904 943 'url09': (u'{% url метка_оператора_2 tag=v %}', {'v': 'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), 944 'url10': (u'{% url "метка_оператора" v %}', {'v': u'Ω'}, '/url_tag/%D0%AE%D0%BD%D0%B8%D0%BA%D0%BE%D0%B4/%CE%A9/'), 905 945 906 946 # Failures 907 947 'url-fail01': ('{% url %}', {}, template.TemplateSyntaxError),