Changeset 1379
- Timestamp:
- 11/23/05 17:10:17 (2 years ago)
- Files:
-
- django/trunk/django/conf/global_settings.py (modified) (1 diff)
- django/trunk/django/conf/project_template/settings.py (modified) (1 diff)
- django/trunk/django/core/template/defaulttags.py (modified) (2 diffs)
- django/trunk/django/core/template/__init__.py (modified) (13 diffs)
- django/trunk/django/core/template/loader.py (modified) (8 diffs)
- django/trunk/django/core/template/loaders/app_directories.py (modified) (1 diff)
- django/trunk/django/core/template/loaders/eggs.py (modified) (1 diff)
- django/trunk/django/core/template/loaders/filesystem.py (modified) (1 diff)
- django/trunk/django/views/debug.py (modified) (7 diffs)
- django/trunk/tests/othertests/templates.py (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
django/trunk/django/conf/global_settings.py
r1303 r1379 9 9 10 10 DEBUG = False 11 TEMPLATE_DEBUG = False 11 12 12 13 # Whether to use the "Etag" header. This saves bandwidth but slows down performance. django/trunk/django/conf/project_template/settings.py
r958 r1379 2 2 3 3 DEBUG = True 4 TEMPLATE_DEBUG = DEBUG 4 5 5 6 ADMINS = ( django/trunk/django/core/template/defaulttags.py
r1098 r1379 193 193 grouper = resolve_variable_with_filters('var.%s' % self.expression, \ 194 194 Context({'var': obj})) 195 # TODO: Is this a sensible way to determine equality? 195 196 if output and repr(output[-1]['grouper']) == repr(grouper): 196 197 output[-1]['list'].append(obj) … … 629 630 try: 630 631 LoadNode.load_taglib(taglib) 631 except ImportError :632 raise TemplateSyntaxError, "'%s' is not a valid tag library " % taglib632 except ImportError, e: 633 raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e) 633 634 return LoadNode(taglib) 634 635 django/trunk/django/core/template/__init__.py
r1207 r1379 56 56 """ 57 57 import re 58 from django.conf.settings import DEFAULT_CHARSET 58 from django.conf.settings import DEFAULT_CHARSET, TEMPLATE_DEBUG 59 59 60 60 __all__ = ('Template','Context','compile_string') … … 75 75 ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.' 76 76 77 # what to report as the origin for templates that come from non-loader sources 78 # (e.g. strings) 79 UNKNOWN_SOURCE="<unknown source>" 80 77 81 # match a variable or block tag and capture the entire tag, including start/end delimiters 78 82 tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), … … 102 106 pass 103 107 108 class Origin(object): 109 def __init__(self, name): 110 self.name = name 111 112 def reload(self): 113 raise NotImplementedException 114 115 def __str__(self): 116 return self.name 117 118 class StringOrigin(Origin): 119 def __init__(self, source): 120 super(StringOrigin, self).__init__(UNKNOWN_SOURCE) 121 self.source = source 122 123 def reload(self): 124 return self.source 125 104 126 class Template: 105 def __init__(self, template_string ):127 def __init__(self, template_string, origin=None): 106 128 "Compilation stage" 107 self.nodelist = compile_string(template_string) 129 if TEMPLATE_DEBUG and origin == None: 130 origin = StringOrigin(template_string) 131 # Could do some crazy stack-frame stuff to record where this string 132 # came from... 133 self.nodelist = compile_string(template_string, origin) 108 134 109 135 def __iter__(self): … … 116 142 return self.nodelist.render(context) 117 143 118 def compile_string(template_string ):144 def compile_string(template_string, origin): 119 145 "Compiles template_string into NodeList ready for rendering" 120 tokens = tokenize(template_string)121 parser = Parser(tokens)146 lexer = lexer_factory(template_string, origin) 147 parser = parser_factory(lexer.tokenize()) 122 148 return parser.parse() 123 149 … … 164 190 return False 165 191 192 def get(self, key, otherwise): 193 for dict in self.dicts: 194 if dict.has_key(key): 195 return dict[key] 196 return otherwise 197 166 198 def update(self, other_dict): 167 199 "Like dict.update(). Pushes an entire dictionary's keys and values onto the context." … … 175 207 def __str__(self): 176 208 return '<%s token: "%s...">' % ( 177 {TOKEN_TEXT: 'Text', TOKEN_VAR:'Var', TOKEN_BLOCK:'Block'}[self.token_type],209 {TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block'}[self.token_type], 178 210 self.contents[:20].replace('\n', '') 179 211 ) 180 212 181 def tokenize(template_string): 182 "Return a list of tokens from a given template_string" 183 # remove all empty strings, because the regex has a tendency to add them 184 bits = filter(None, tag_re.split(template_string)) 185 return map(create_token, bits) 186 187 def create_token(token_string): 188 "Convert the given token string into a new Token object and return it" 189 if token_string.startswith(VARIABLE_TAG_START): 190 return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) 191 elif token_string.startswith(BLOCK_TAG_START): 192 return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) 193 else: 194 return Token(TOKEN_TEXT, token_string) 195 196 class Parser: 213 def __repr__(self): 214 return '<%s token: "%s">' % ( 215 {TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block'}[self.token_type], 216 self.contents[:].replace('\n', '') 217 ) 218 219 class Lexer(object): 220 def __init__(self, template_string, origin): 221 self.template_string = template_string 222 self.origin = origin 223 224 def tokenize(self): 225 "Return a list of tokens from a given template_string" 226 # remove all empty strings, because the regex has a tendency to add them 227 bits = filter(None, tag_re.split(self.template_string)) 228 return map(self.create_token, bits) 229 230 def create_token(self,token_string): 231 "Convert the given token string into a new Token object and return it" 232 if token_string.startswith(VARIABLE_TAG_START): 233 token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) 234 elif token_string.startswith(BLOCK_TAG_START): 235 token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) 236 else: 237 token = Token(TOKEN_TEXT, token_string) 238 return token 239 240 class DebugLexer(Lexer): 241 def __init__(self, template_string, origin): 242 super(DebugLexer, self).__init__(template_string, origin) 243 244 def tokenize(self): 245 "Return a list of tokens from a given template_string" 246 token_tups, upto = [], 0 247 for match in tag_re.finditer(self.template_string): 248 start, end = match.span() 249 if start > upto: 250 token_tups.append( (self.template_string[upto:start], (upto, start)) ) 251 upto = start 252 token_tups.append( (self.template_string[start:end], (start,end)) ) 253 upto = end 254 last_bit = self.template_string[upto:] 255 if last_bit: 256 token_tups.append( (last_bit, (upto, upto + len(last_bit))) ) 257 return [self.create_token(tok, (self.origin, loc)) for tok, loc in token_tups] 258 259 def create_token(self, token_string, source): 260 token = super(DebugLexer, self).create_token(token_string) 261 token.source = source 262 return token 263 264 class Parser(object): 197 265 def __init__(self, tokens): 198 266 self.tokens = tokens 199 267 200 268 def parse(self, parse_until=[]): 201 nodelist = NodeList()269 nodelist = self.create_nodelist() 202 270 while self.tokens: 203 271 token = self.next_token() 204 272 if token.token_type == TOKEN_TEXT: 205 nodelist.append(TextNode(token.contents))273 self.extend_nodelist(nodelist, TextNode(token.contents), token) 206 274 elif token.token_type == TOKEN_VAR: 207 275 if not token.contents: 208 raise TemplateSyntaxError, "Empty variable tag" 209 nodelist.append(VariableNode(token.contents)) 276 self.empty_variable(token) 277 var_node = self.create_variable_node(token.contents) 278 self.extend_nodelist(nodelist, var_node,token) 210 279 elif token.token_type == TOKEN_BLOCK: 211 280 if token.contents in parse_until: … … 216 285 command = token.contents.split()[0] 217 286 except IndexError: 218 raise TemplateSyntaxError, "Empty block tag" 287 self.empty_block_tag(token) 288 # execute callback function for this tag and append resulting node 289 self.enter_command(command, token) 219 290 try: 220 # execute callback function for this tag and append resulting node 221 nodelist.append(registered_tags[command](self, token)) 291 compile_func = registered_tags[command] 222 292 except KeyError: 223 raise TemplateSyntaxError, "Invalid block tag: '%s'" % command 293 self.invalid_block_tag(token, command) 294 try: 295 compiled_result = compile_func(self, token) 296 except TemplateSyntaxError, e: 297 if not self.compile_function_error(token, e): 298 raise 299 self.extend_nodelist(nodelist, compiled_result, token) 300 self.exit_command() 224 301 if parse_until: 225 raise TemplateSyntaxError, "Unclosed tag(s): '%s'" % ', '.join(parse_until)302 self.unclosed_block_tag(parse_until) 226 303 return nodelist 304 305 def create_variable_node(self, contents): 306 return VariableNode(contents) 307 308 def create_nodelist(self): 309 return NodeList() 310 311 def extend_nodelist(self, nodelist, node, token): 312 nodelist.append(node) 313 314 def enter_command(self, command, token): 315 pass 316 317 def exit_command(self): 318 pass 319 320 def error(self, token, msg ): 321 return TemplateSyntaxError(msg) 322 323 def empty_variable(self, token): 324 raise self.error( token, "Empty variable tag") 325 326 def empty_block_tag(self, token): 327 raise self.error( token, "Empty block tag") 328 329 def invalid_block_tag(self, token, command): 330 raise self.error( token, "Invalid block tag: '%s'" % command) 331 332 def unclosed_block_tag(self, parse_until): 333 raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until)) 334 335 def compile_function_error(self, token, e): 336 pass 227 337 228 338 def next_token(self): … … 234 344 def delete_first_token(self): 235 345 del self.tokens[0] 346 347 class DebugParser(Parser): 348 def __init__(self, lexer): 349 super(DebugParser, self).__init__(lexer) 350 self.command_stack = [] 351 352 def enter_command(self, command, token): 353 self.command_stack.append( (command, token.source) ) 354 355 def exit_command(self): 356 self.command_stack.pop() 357 358 def error(self, token, msg): 359 return self.source_error(token.source, msg) 360 361 def source_error(self, source,msg): 362 e = TemplateSyntaxError(msg) 363 e.source = source 364 return e 365 366 def create_nodelist(self): 367 return DebugNodeList() 368 369 def create_variable_node(self, contents): 370 return DebugVariableNode(contents) 371 372 def extend_nodelist(self, nodelist, node, token): 373 node.source = token.source 374 super(DebugParser, self).extend_nodelist(nodelist, node, token) 375 376 def unclosed_block_tag(self, parse_until): 377 (command, source) = self.command_stack.pop() 378 msg = "Unclosed tag '%s'. Looking for one of: %s " % (command, ', '.join(parse_until)) 379 raise self.source_error( source, msg) 380 381 def compile_function_error(self, token, e): 382 if not hasattr(e, 'source'): 383 e.source = token.source 384 385 if TEMPLATE_DEBUG: 386 lexer_factory = DebugLexer 387 parser_factory = DebugParser 388 else: 389 lexer_factory = Lexer 390 parser_factory = Parser 236 391 237 392 class TokenParser: … … 317 472 return s 318 473 319 class FilterParser: 474 475 476 477 filter_raw_string = r""" 478 ^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s| 479 ^"(?P<constant>%(str)s)"| 480 ^(?P<var>[%(var_chars)s]+)| 481 (?:%(filter_sep)s 482 (?P<filter_name>\w+) 483 (?:%(arg_sep)s 484 (?: 485 %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s| 486 "(?P<arg>%(str)s)" 487 ) 488 )? 489 )""" % { 490 'str': r"""[^"\\]*(?:\\.[^"\\]*)*""", 491 'var_chars': "A-Za-z0-9\_\." , 492 'filter_sep': re.escape(FILTER_SEPARATOR), 493 'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR), 494 'i18n_open' : re.escape("_("), 495 'i18n_close' : re.escape(")"), 496 } 497 498 filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "") 499 filter_re = re.compile(filter_raw_string) 500 501 class FilterParser(object): 320 502 """ 321 503 Parses a variable token and its optional filters (all as a single string), … … 332 514 get_filters_from_token helper function. 333 515 """ 334 def __init__(self, s): 335 self.s = s 336 self.i = -1 337 self.current = '' 338 self.filters = [] 339 self.current_filter_name = None 340 self.current_filter_arg = None 341 # First read the variable part. Decide whether we need to parse a 342 # string or a variable by peeking into the stream. 343 if self.peek_char() in ('_', '"', "'"): 344 self.var = self.read_constant_string_token() 345 else: 346 self.var = self.read_alphanumeric_token() 347 if not self.var: 348 raise TemplateSyntaxError, "Could not read variable name: '%s'" % self.s 349 if self.var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or self.var[0] == '_': 350 raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % self.var 351 # Have we reached the end? 352 if self.current is None: 353 return 354 if self.current != FILTER_SEPARATOR: 355 raise TemplateSyntaxError, "Bad character (expecting '%s') '%s'" % (FILTER_SEPARATOR, self.current) 356 # We have a filter separator; start reading the filters 357 self.read_filters() 358 359 def peek_char(self): 360 try: 361 return self.s[self.i+1] 362 except IndexError: 363 return None 364 365 def next_char(self): 366 self.i = self.i + 1 367 try: 368 self.current = self.s[self.i] 369 except IndexError: 370 self.current = None 371 372 def read_constant_string_token(self): 373 """ 374 Reads a constant string that must be delimited by either " or ' 375 characters. The string is returned with its delimiters. 376 """ 377 val = '' 378 qchar = None 379 i18n = False 380 self.next_char() 381 if self.current == '_': 382 i18n = True 383 self.next_char() 384 if self.current != '(': 385 raise TemplateSyntaxError, "Bad character (expecting '(') '%s'" % self.current 386 self.next_char() 387 if not self.current in ('"', "'"): 388 raise TemplateSyntaxError, "Bad character (expecting '\"' or ''') '%s'" % self.current 389 qchar = self.current 390 val += qchar 391 while 1: 392 self.next_char() 393 if self.current == qchar: 394 break 395 val += self.current 396 val += self.current 397 self.next_char() 398 if i18n: 399 if self.current != ')': 400 raise TemplateSyntaxError, "Bad character (expecting ')') '%s'" % self.current 401 self.next_char() 402 val = qchar+_(val.strip(qchar))+qchar 403 return val 404 405 def read_alphanumeric_token(self): 406 """ 407 Reads a variable name or filter name, which are continuous strings of 408 alphanumeric characters + the underscore. 409 """ 410 var = '' 411 while 1: 412 self.next_char() 413 if self.current is None: 414 break 415 if self.current not in ALLOWED_VARIABLE_CHARS: 416 break 417 var += self.current 418 return var 419 420 def read_filters(self): 421 while 1: 422 filter_name, arg = self.read_filter() 423 if not registered_filters.has_key(filter_name): 424 raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name 425 if registered_filters[filter_name][1] == True and arg is None: 426 raise TemplateSyntaxError, "Filter '%s' requires an argument" % filter_name 427 if registered_filters[filter_name][1] == False and arg is not None: 428 raise TemplateSyntaxError, "Filter '%s' should not have an argument (argument is %r)" % (filter_name, arg) 429 self.filters.append((filter_name, arg)) 430 if self.current is None: 431 break 432 433 def read_filter(self): 434 self.current_filter_name = self.read_alphanumeric_token() 435 self.current_filter_arg = None 436 # Have we reached the end? 437 if self.current is None: 438 return (self.current_filter_name, None) 439 # Does the filter have an argument? 440 if self.current == FILTER_ARGUMENT_SEPARATOR: 441 self.current_filter_arg = self.read_arg() 442 return (self.current_filter_name, self.current_filter_arg) 443 # Next thing MUST be a pipe 444 if self.current != FILTER_SEPARATOR: 445 raise TemplateSyntaxError, "Bad character (expecting '%s') '%s'" % (FILTER_SEPARATOR, self.current) 446 return (self.current_filter_name, self.current_filter_arg) 447 448 def read_arg(self): 449 # First read a " or a _(" 450 self.next_char() 451 translated = False 452 if self.current == '_': 453 self.next_char() 454 if self.current != '(': 455 raise TemplateSyntaxError, "Bad character (expecting '(') '%s'" % self.current 456 translated = True 457 self.next_char() 458 if self.current != '"': 459 raise TemplateSyntaxError, "Bad character (expecting '\"') '%s'" % self.current 460 self.escaped = False 461 arg = '' 462 while 1: 463 self.next_char() 464 if self.current == '"' and not self.escaped: 465 break 466 if self.current == '\\' and not self.escaped: 467 self.escaped = True 468 continue 469 if self.current == '\\' and self.escaped: 470 arg += '\\' 471 self.escaped = False 472 continue 473 if self.current == '"' and self.escaped: 474 arg += '"' 475 self.escaped = False 476 continue 477 if self.escaped and self.current not in '\\"': 478 raise TemplateSyntaxError, "Unescaped backslash in '%s'" % self.s 479 if self.current is None: 480 raise TemplateSyntaxError, "Unexpected end of argument in '%s'" % self.s 481 arg += self.current 482 # self.current must now be '"' 483 self.next_char() 484 if translated: 485 if self.current != ')': 486 raise TemplateSyntaxError, "Bad character (expecting ')') '%s'" % self.current 487 self.next_char() 488 arg = _(arg) 489 return arg 516 def __init__(self, token): 517 matches = filter_re.finditer(token) 518 var = None 519 filters = [] 520 upto = 0 521 for match in matches: 522 start = match.start() 523 if upto != start: 524 raise TemplateSyntaxError, "Could not parse some characters: %s|%s|%s" % \ 525 (token[:upto], token[upto:start], token[start:]) 526 if var == None: 527 var, constant, i18n_constant = match.group("var", "constant", "i18n_constant") 528 if i18n_constant: 529 var = '"%s"' % _(i18n_constant) 530 elif constant: 531 var = '"%s"' % constant 532 upto = match.end() 533 if var == None: 534 raise TemplateSyntaxError, "Could not find variable at start of %s" % token 535 elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_': 536 raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % var 537 else: 538 filter_name = match.group("filter_name") 539 arg, i18n_arg = match.group("arg","i18n_arg") 540 if i18n_arg: 541 arg =_(i18n_arg.replace('\\', '')) 542 if arg: 543 arg = arg.replace('\\', '') 544 if not registered_filters.has_key(filter_name): 545 raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name 546 if registered_filters[filter_name][1] == True and arg is None: 547 raise TemplateSyntaxError, "Filter '%s' requires an argument" % filter_name 548 if registered_filters[filter_name][1] == False and arg is not None: 549 raise TemplateSyntaxError, "Filter '%s' should not have an argument (argument is %r)" % (filter_name, arg) 550 filters.append( (filter_name,arg) ) 551 upto = match.end() 552 if upto != len(token): 553 raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:] 554 self.var , self.filters = var, filters 490 555 491 556 def get_filters_from_token(token): … … 581 646 for node in self: 582 647 if isinstance(node, Node): 583 bits.append( node.render(context))648 bits.append(self.render_node(node, context)) 584 649 else: 585 650 bits.append(node) … … 593 658 return nodes 594 659 660 def render_node(self, node, context): 661 return(node.render(context)) 662 663 class DebugNodeList(NodeList): 664 def render_node(self, node, context): 665 try: 666 result = node.render(context) 667 except TemplateSyntaxError, e: 668 if not hasattr(e, 'source'): 669 e.source = node.source 670 raise 671 except Exception: 672 from sys import exc_info 673 wrapped = TemplateSyntaxError('Caught an exception while rendering.') 674 wrapped.source = node.source 675 wrapped.exc_info = exc_info() 676 raise wrapped 677 return result 678 595 679 class TextNode(Node): 596 680 def __init__(self, s): … … 610 694 return "<Variable Node: %s>" % self.var_string 611 695 696 def encode_output(self, output): 697 # Check type so that we don't run str() on a Unicode object 698 if not isinstance(output, basestring): 699 return str(output) 700 elif isinstance(output, unicode): 701 return output.encode(DEFAULT_CHARSET) 702 else: 703 return output 704 612 705 def render(self, context): 613 706 output = resolve_variable_with_filters(self.var_string, context) 614 # Check type so that we don't run str() on a Unicode object 615 if not isinstance(output, basestring): 616 output = str(output) 617 elif isinstance(output, unicode): 618 output = output.encode(DEFAULT_CHARSET) 619 return output 707 return self.encode_output(output) 708 709 class DebugVariableNode(VariableNode): 710 def render(self, context): 711 try: 712 output = resolve_variable_with_filters(self.var_string, context) 713 except TemplateSyntaxError, e: 714 if not hasattr(e, 'source'): 715 e.source = self.source 716 raise 717 return self.encode_output(output) 620 718 621 719 def register_tag(token_command, callback_function): django/trunk/django/core/template/loader.py
r1349 r1379 8 8 # name is the template name. 9 9 # dirs is an optional list of directories to search instead of TEMPLATE_DIRS. 10 # 11 # The loader should return a tuple of (template_source, path). The path returned 12 # might be shown to the user for debugging purposes, so it should identify where 13 # the template was loaded from. 10 14 # 11 15 # Each loader should have an "is_usable" attribute set. This is a boolean that … … 18 22 19 23 from django.core.exceptions import ImproperlyConfigured 20 from django.core.template import Template, Context, Node, TemplateDoesNotExist, TemplateSyntaxError, resolve_variable_with_filters, resolve_variable, register_tag21 from django.conf.settings import TEMPLATE_LOADERS 24 from django.core.template import Origin, StringOrigin, Template, Context, Node, TemplateDoesNotExist, TemplateSyntaxError, resolve_variable_with_filters, resolve_variable, register_tag 25 from django.conf.settings import TEMPLATE_LOADERS, TEMPLATE_DEBUG 22 26 23 27 template_source_loaders = [] … … 39 43 template_source_loaders.append(func) 40 44 41 def load_template_source(name, dirs=None): 45 class LoaderOrigin(Origin): 46 def __init__(self, display_name, loader, name, dirs): 47 super(LoaderOrigin, self).__init__(display_name) 48 self.loader, self.loadname, self.dirs = loader, name, dirs 49 50 def reload(self): 51 return self.loader(self.loadname, self.dirs)[0] 52 53 def make_origin(display_name, loader, name, dirs): 54 if TEMPLATE_DEBUG: 55 return LoaderOrigin(display_name, loader, name, dirs) 56 else: 57 return None 58 59 def find_template_source(name, dirs=None): 42 60 for loader in template_source_loaders: 43 61 try: 44 return loader(name, dirs) 62 source, display_name = loader(name, dirs) 63 return (source, make_origin(display_name, loader, name, dirs)) 45 64 except TemplateDoesNotExist: 46 65 pass 47 66 raise TemplateDoesNotExist, name 48 67 68 def load_template_source(name, dirs=None): 69 find_template_source(name, dirs)[0] 70 49 71 class ExtendsError(Exception): 50 72 pass … … 55 77 handling template inheritance recursively. 56 78 """ 57 return get_template_from_string( load_template_source(template_name))58 59 def get_template_from_string(source ):79 return get_template_from_string(*find_template_source(template_name)) 80 81 def get_template_from_string(source, origin=None ): 60 82 """ 61 83 Returns a compiled Template object for the given template code, 62 84 handling template inheritance recursively. 63 85 """ 64 return Template(source )86 return Template(source, origin) 65 87 66 88 def render_to_string(template_name, dictionary=None, context_instance=None): … … 135 157 raise TemplateSyntaxError, error_msg 136 158 try: 137 return get_template_from_string( load_template_source(parent, self.template_dirs))159 return get_template_from_string(*find_template_source(parent, self.template_dirs)) 138 160 except TemplateDoesNotExist: 139 161 raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent … … 166 188 t = get_template(template_path) 167 189 self.template = t 168 except: 190 except Exception, e: 191 if TEMPLATE_DEBUG: 192 raise 169 193 self.template = None 170 194 … … 184 208 t = get_template(template_name) 185 209 return t.render(context) 210 except TemplateSyntaxError, e: 211 if TEMPLATE_DEBUG: 212 raise 213 return '' 186 214 except: 187 215 return '' # Fail silently for invalid included templates. … … 237 265 {% include "foo/some_include" %} 238 266 """ 267 239 268 bits = token.contents.split() 240 269 if len(bits) != 2: django/trunk/django/core/template/loaders/app_directories.py
r985 r1379 32 32 filepath = os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION 33 33 try: 34 return open(filepath).read()34 return (open(filepath).read(), filepath) 35 35 except IOError: 36 36 pass django/trunk/django/core/template/loaders/eggs.py
r889 r1379 19 19 for app in INSTALLED_APPS: 20 20 try: 21 return resource_string(app, pkg_name)21 return (resource_string(app, pkg_name), 'egg:%s:%s ' % (app, pkg_name)) 22 22 except: 23 23 pass django/trunk/django/core/template/loaders/filesystem.py
r890 r1379 12 12 filepath = os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION 13 13 try: 14 return open(filepath).read()14 return (open(filepath).read(), filepath) 15 15 except IOError: 16 16 tried.append(filepath) django/trunk/django/views/debug.py
r1351 r1379 1 import re2 import os3 import sys4 import inspect5 1 from django.conf import settings 2 from django.core.template import Template, Context 3 from django.utils.html import escape 4 from django.utils.httpwrappers import HttpResponseServerError, HttpResponseNotFound 5 import inspect, os, re, sys 6 from itertools import count, izip 6 7 from os.path import dirname, join as pathjoin 7 from django.core.template import Template, Context8 from django.utils.httpwrappers import HttpResponseServerError, HttpResponseNotFound9 8 10 9 HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD') 10 11 def linebreak_iter(template_source): 12 newline_re = re.compile("^", re.M) 13 for match in newline_re.finditer(template_source): 14 yield match.start() 15 yield len(template_source) + 1 16 17 def get_template_exception_info(exc_type, exc_value, tb): 18 origin, (start, end) = exc_value.source 19 template_source = origin.reload() 20 context_lines = 10 21 line = 0 22 upto = 0 23 source_lines = [] 24 linebreaks = izip(count(0), linebreak_iter(template_source)) 25 linebreaks.next() # skip the nothing before initial line start 26 for num, next in linebreaks: 27 if start >= upto and end <= next: 28 line = num 29 before = escape(template_source[upto:start]) 30 during = escape(template_source[start:end]) 31 after = escape(template_source[end:next - 1]) 32 source_lines.append( (num, escape(template_source[upto:next - 1])) ) 33 upto = next 34 total = len(source_lines) 35 36 top = max(0, line - context_lines) 37 bottom = min(total, line + 1 + context_lines) 38 39 template_info = { 40 'message': exc_value.args[0], 41 'source_lines': source_lines[top:bottom], 42 'before': before, 43 'during': during, 44 'after': after, 45 'top': top , 46 'bottom': bottom , 47 'total': total, 48 'line': line, 49 'name': origin.name, 50 } 51 exc_info = hasattr(exc_value, 'exc_info') and exc_value.exc_info or (exc_type, exc_value, tb) 52 return exc_info + (template_info,) 11 53 12 54 def technical_500_response(request, exc_type, exc_value, tb): … … 15 57 the values returned from sys.exc_info() and friends. 16 58 """ 59 template_info = None 60 if settings.TEMPLATE_DEBUG and hasattr(exc_value, 'source'): 61 exc_type, exc_value, tb, template_info = get_template_exception_info(exc_type, exc_value, tb) 17 62 frames = [] 18 63 while tb is not None: … … 22 67 pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7) 23 68 frames.append({ 24 'tb' : tb,25 'filename' : filename,26 'function' : function,27 'lineno' : lineno,28 'vars' : tb.tb_frame.f_locals.items(),29 'id' : id(tb),30 'pre_context' : pre_context,31 'context_line' : context_line,32 'post_context' : post_context,33 'pre_context_lineno' : pre_context_lineno,69 'tb': tb, 70 'filename': filename, 71 'function': function, 72 'lineno': lineno, 73 'vars': tb.tb_frame.f_locals.items(), 74 'id': id(tb), 75 'pre_context': pre_context, 76 'context_line': context_line, 77 'post_context': post_context, 78 'pre_context_lineno': pre_context_lineno, 34 79 }) 35 80 tb = tb.tb_next … … 47 92 t = Template(TECHNICAL_500_TEMPLATE) 48 93 c = Context({ 49 'exception_type' : exc_type.__name__,50 'exception_value' : exc_value,51 'frames' : frames,52 'lastframe' : frames[-1],53 'request' : request,54 'request_protocol' : os.environ.get("HTTPS") == "on" and "https" or "http",55 'settings' : settings_dict,56 94 'exception_type': exc_type.__name__, 95 'exception_value': exc_value, 96 'frames': frames, 97 'lastframe': frames[-1], 98 'request': request, 99 'request_protocol': os.environ.get("HTTPS") == "on" and "https" or "http", 100 'settings': settings_dict, 101 'template_info': template_info, 57 102 }) 58 103 return HttpResponseServerError(t.render(c), mimetype='text/html') … … 70 115 t = Template(TECHNICAL_404_TEMPLATE) 71 116 c = Context({ 72 'root_urlconf' : settings.ROOT_URLCONF,73 'urlpatterns' : tried,74 'reason' : str(exception),75 'request' : request,76 'request_protocol' : os.environ.get("HTTPS") == "on" and "https" or "http",77 'settings' : dict([(k, getattr(settings, k)) for k in dir(settings) if k.isupper()]),117 'root_urlconf': settings.ROOT_URLCONF, 118 'urlpatterns': tried, 119 'reason': str(exception), 120 'request': request, 121 'request_protocol': os.environ.get("HTTPS") == "on" and "https" or "http", 122 'settings': dict([(k, getattr(settings, k)) for k in dir(settings) if k.isupper()]), 78 123 }) 79 124 return HttpResponseNotFound(t.render(c), mimetype='text/html') … … 145 190 #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; } 146 191 #requestinfo h3 { margin-bottom:-1em; } 192 table.source td { font-family: monospace; white-space: pre; } 193 span.specific { background:#ffcab7; } 194 .error { background: #ffc; } 147 195 </style> 148 196 <script type="text/javascript"> … … 222 270 </table> 223 271 </div> 224 272 {% if template_info %} 273 <div id="template"> 274 <h2>Template</h2> 275 In template {{ template_info.name }}, error at line {{ template_info.line }} 276 <div>{{ template_info.message|escape }}</div> 277 <table class="source{% if template_info.top %} cut-top{% endif %}{% ifnotequal template_info.bottom template_info.total %} cut-bottom{% endifnotequal %}"> 278 {% for source_line
