diff --git a/django/contrib/admin/media/css/template_errors.css b/django/contrib/admin/media/css/template_errors.css new file mode 100644 index 0000000000..6a2562a3a1 --- /dev/null +++ b/django/contrib/admin/media/css/template_errors.css @@ -0,0 +1,8 @@ +table.source td{ + font-family: monospace; + white-space: pre; +} + +span.specific{ + background:#ffcab7; +} \ No newline at end of file diff --git a/django/contrib/admin/templates/admin/template_debug.html b/django/contrib/admin/templates/admin/template_debug.html index 779d571e54..f6ef796be3 100644 --- a/django/contrib/admin/templates/admin/template_debug.html +++ b/django/contrib/admin/templates/admin/template_debug.html @@ -1,26 +1,36 @@ {% extends "admin/base_site" %} - +{% load adminmedia %} +{% block extrahead%} {%endblock%} {% block content %}
-

Error in Template

+

{%trans "Error in Template" %}

+{%blocktrans %} +In template {{name}}, error at line {{line}}: +{%endblocktrans %}
 {{message|escape}}
 {{traceback|escape}}
 
- {%if top%} ... {%endif%} -
+   
+   {% for source_line in source_lines %}
+   {%ifequal source_line.0 line %}
+	   
+	   
+   {%else%}
+      
+	  
+   {%endifequal%}
+   {%endfor%}
+   
{{source_line.0}} {{before}}{{during}}{{after}}
{{source_line.0}} {{source_line.1}}
- {% for source_line in source_lines %}{% ifequal source_line.0 line %}{{source_line.0|rjust:"5"}}:{{ source_line.1}} - {% else %}{{source_line.0|rjust:"5"}}:{{ source_line.1 }} - {% endifequal %}{% endfor %} -
{%ifnotequal bottom total%} ... {%endifnotequal%}
+ {% endblock %} \ No newline at end of file diff --git a/django/core/template/__init__.py b/django/core/template/__init__.py index 3e37b114a1..8bea26b658 100644 --- a/django/core/template/__init__.py +++ b/django/core/template/__init__.py @@ -77,9 +77,6 @@ ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01 #What to report as the origin of templates that come from non loader sources (ie strings) UNKNOWN_SOURCE="" -#match starts of lines -newline_re = re.compile("^", re.M); - # match a variable or block tag and capture the entire tag, including start/end delimiters tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END))) @@ -242,31 +239,20 @@ class DebugLexer(Lexer): def __init__(self, template_string, origin): super(DebugLexer,self).__init__(template_string, origin) - def find_linebreaks(self, template_string): - for match in newline_re.finditer(template_string): - yield match.start() - yield len(template_string) + 1 - def tokenize(self): "Return a list of tokens from a given template_string" token_tups, upto = [], 0 - lines = enumerate(self.find_linebreaks(self.template_string)) - line, next_linebreak = lines.next() for match in tag_re.finditer(self.template_string): - while next_linebreak <= upto: - line, next_linebreak = lines.next() start, end = match.span() if start > upto: - token_tups.append( (self.template_string[upto:start], line) ) + token_tups.append( (self.template_string[upto:start], (upto, start) ) ) upto = start - while next_linebreak <= upto: - line, next_linebreak = lines.next() - token_tups.append( (self.template_string[start:end], line) ) + token_tups.append( (self.template_string[start:end], (start,end) ) ) upto = end last_bit = self.template_string[upto:] if last_bit: - token_tups.append( (last_bit, line) ) - return [ self.create_token(tok, (self.origin, line)) for tok, line in token_tups] + token_tups.append( (last_bit, (upto, upto + len(last_bit) ) ) ) + return [ self.create_token(tok, (self.origin, loc)) for tok, loc in token_tups] def create_token(self, token_string, source): token = super(DebugLexer, self).create_token(token_string) @@ -298,7 +284,7 @@ class Parser(object): except IndexError: self.empty_block_tag(token) # execute callback function for this tag and append resulting node - self.enter_command(command, token); + self.enter_command(command, token) try: compile_func = registered_tags[command] except KeyError: @@ -309,9 +295,9 @@ class Parser(object): if not self.compile_function_error(token, e): raise self.extend_nodelist(nodelist, compiled_result, token) - self.exit_command(); + self.exit_command() if parse_until: - self.unclosed_block_tag(token, parse_until) + self.unclosed_block_tag(parse_until) return nodelist def create_variable_node(self, contents): @@ -329,17 +315,20 @@ class Parser(object): def exit_command(self): pass + def error(self, token, msg ): + return TemplateSyntaxError(msg) + def empty_variable(self, token): - raise TemplateSyntaxError, "Empty variable tag" + raise self.error( token, "Empty variable tag") def empty_block_tag(self, token): - raise TemplateSyntaxError, "Empty block tag" + raise self.error( token, "Empty block tag") def invalid_block_tag(self, token, command): - raise TemplateSyntaxError, "Invalid block tag: %s" % (command) + raise self.error( token, "Invalid block tag: '%s'" % command) - def unclosed_block_tag(self, token, parse_until): - raise TemplateSyntaxError, "Unclosed tags: %s " % ', '.join(parse_until) + def unclosed_block_tag(self, parse_until): + raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until)) def compile_function_error(self, token, e): pass @@ -352,9 +341,6 @@ class Parser(object): def delete_first_token(self): del self.tokens[0] - -def format_source(source): - return "at %s, line %d" % source class DebugParser(Parser): def __init__(self, lexer): @@ -367,14 +353,14 @@ class DebugParser(Parser): def exit_command(self): self.command_stack.pop() - def error(self, source, msg): + def error(self, token, msg): + return self.source_error(token.source, msg) + + def source_error(self, source,msg): e = TemplateSyntaxError(msg) e.source = source return e - def format_source(self, source): - return "at %s, line %d" % source - def create_nodelist(self): return DebugNodeList() @@ -384,21 +370,12 @@ class DebugParser(Parser): def extend_nodelist(self, nodelist, node, token): node.source = token.source super(DebugParser, self).extend_nodelist(nodelist, node, token) - - def empty_variable(self, token): - raise self.error( token.source, "Empty variable tag %s" % format_source(token.source)) - def empty_block_tag(self, token): - raise self.error( token.source, "Empty block tag %s" % format_source(token.source)) - - def invalid_block_tag(self, token, command): - raise self.error( token.source, "Invalid block tag: '%s' %s" % (command, format_source(token.source))) - - def unclosed_block_tag(self, token, parse_until): - (command, (origin,line)) = self.command_stack.pop() - msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \ - (command, origin, line, ', '.join(parse_until) ) - raise self.error( (origin,line), msg) + def unclosed_block_tag(self, parse_until): + (command, source) = self.command_stack.pop() + msg = "Unclosed tag '%s'. Looking for one of: %s " % \ + (command, ', '.join(parse_until) ) + raise self.source_error( source, msg) def compile_function_error(self, token, e): if not hasattr(e, 'source'): @@ -843,14 +820,14 @@ class DebugNodeList(NodeList): e.source = node.source raise except Exception, e: + from traceback import extract_tb, format_list, format_exception_only from sys import exc_info t,v,tb = exc_info() frames = extract_tb(tb) frames.pop(0) - wrapped = TemplateSyntaxError( ' While rendering %s , caught exception:\n %s' - % ( format_source(node.source), - "".join(format_exception_only(t,v)).replace('\n',''))) + wrapped = TemplateSyntaxError( 'Caught exception:\n %s' + % "".join(format_exception_only(t,v)).replace('\n','')) wrapped.source = node.source wrapped.traceback = "".join(format_list(frames)) raise wrapped diff --git a/django/core/template/loader.py b/django/core/template/loader.py index cb687a7edb..925f4448d2 100644 --- a/django/core/template/loader.py +++ b/django/core/template/loader.py @@ -44,10 +44,10 @@ for path in TEMPLATE_LOADERS: class LoaderOrigin(Origin): def __init__(self, display_name, loader, name, dirs): super(LoaderOrigin, self).__init__(display_name) - self.loader, self.name, self.dirs = loader, name, dirs + self.loader, self.loadname, self.dirs = loader, name, dirs def reload(self): - return self.loader(self.name, self.dirs)[0] + return self.loader(self.loadname, self.dirs)[0] def make_origin(display_name, loader, name, dirs): if TEMPLATE_DEBUG: @@ -99,7 +99,7 @@ def render_to_string(template_name, dictionary=None, context_instance=None): if context_instance: context_instance.update(dictionary) else: - context_instance = Context(dictionary) + context_instance = Context(dictionary) return t.render(context_instance) def select_template(template_name_list): @@ -187,6 +187,8 @@ class ConstantIncludeNode(Node): t = get_template(template_path) self.nodelist = t.nodelist except Exception, e: + if TEMPLATE_DEBUG: + raise self.nodelist = None def render(self, context): @@ -202,10 +204,11 @@ class IncludeNode(Node): def render(self, context): try: template_path = resolve_variable(self.template_path_var, context) - print "IncludeNode rendering %s" % template_path t = get_template(template_path) return t.render(context) except Exception, e: + if TEMPLATE_DEBUG: + raise return '' # Fail silently for invalid included templates. def do_block(parser, token): diff --git a/django/middleware/template_debug.py b/django/middleware/template_debug.py index 951cd31959..b31f32e328 100644 --- a/django/middleware/template_debug.py +++ b/django/middleware/template_debug.py @@ -1,30 +1,58 @@ class TemplateDebugMiddleware(object): + def linebreak_iter(self, template_source): + import re + newline_re = re.compile("^", re.M) + for match in newline_re.finditer(template_source): + yield match.start() + yield len(template_source) + 1 + def process_exception(self, request, exception): from django.core.template.loader import render_to_string from django.utils.html import escape from django.utils.httpwrappers import HttpResponseServerError from django.core.extensions import DjangoContext from itertools import count, izip + context_lines = 10 if hasattr(exception, 'source'): - origin, line = exception.source + origin, (start, end) = exception.source template_source = origin.reload() - source_lines = [ (i,s) for (i,s) in izip(count(1), escape(template_source).split("\n"))] + + + line = 0 + upto = 0 + source_lines = [] + linebreaks = izip(count(0), self.linebreak_iter(template_source)) + linebreaks.next() # skip the nothing before initial line start + for num, next in linebreaks: + if start >= upto and end <= next : + line = num + before = escape(template_source[upto:start]) + during = escape(template_source[start:end]) + after = escape(template_source[end:next - 1]) + + source_lines.append( (num, escape(template_source[upto:next - 1])) ) + upto = next + total = len(source_lines) + top = max(0, line - context_lines) bottom = min(total, line + 1 + context_lines) traceback = hasattr(exception, 'traceback') and exception.traceback or '' - return HttpResponseServerError( - render_to_string('template_debug', - DjangoContext(request, { - 'message' : exception.args[0], - 'traceback' : traceback, - 'source_lines' : source_lines[top:bottom], - 'top': top , - 'bottom': bottom , - 'total' : total, - 'line' : line - }), - ) - ) \ No newline at end of file + result = render_to_string('template_debug', + DjangoContext(request, { + 'message' : exception.args[0], + 'traceback' : traceback, + 'source_lines' : source_lines[top:bottom], + 'before' : before, + 'during': during, + 'after': after, + 'top': top , + 'bottom': bottom , + 'total' : total, + 'line' : line, + 'name' : origin.name, + }), + ) + return HttpResponseServerError(result)