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 %}
+ {{source_line.0}} |
+ {{before}}{{during}}{{after}} |
+ {%else%}
+ {{source_line.0}} |
+ {{source_line.1}} |
+ {%endifequal%}
+ {%endfor%}
+
- {% 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)