1
0
mirror of https://github.com/django/django.git synced 2025-07-05 02:09:13 +00:00

Template debug fixes. Now pinpoints character range rather than just line.

git-svn-id: http://code.djangoproject.com/svn/django/branches/new-admin@1144 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Robert Wittams 2005-11-09 19:47:39 +00:00
parent 1906f568cd
commit 8c782b6359
5 changed files with 103 additions and 77 deletions

View File

@ -0,0 +1,8 @@
table.source td{
font-family: monospace;
white-space: pre;
}
span.specific{
background:#ffcab7;
}

View File

@ -1,26 +1,36 @@
{% extends "admin/base_site" %} {% extends "admin/base_site" %}
{% load adminmedia %}
{% block extrahead%} <link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/template_errors.css" /> {%endblock%}
{% block content %} {% block content %}
<div> <div>
<h2> Error in Template </h2> <h2>{%trans "Error in Template" %}</h2>
{%blocktrans %}
In template {{name}}, error at line {{line}}:
{%endblocktrans %}
<pre> <pre>
{{message|escape}} {{message|escape}}
{{traceback|escape}} {{traceback|escape}}
</pre> </pre>
{%if top%} {%if top%}
... ...
{%endif%} {%endif%}
<pre class="source"> <table class="source{%if top%} cut-top{%endif%}{%ifnotequal bottom total%} cut-bottom{%endifnotequal%}">
{% for source_line in source_lines %}
{%ifequal source_line.0 line %}
<tr class="error"><td>{{source_line.0}}</td>
<td> {{before}}<span class="specific">{{during}}</span>{{after}}</td></tr>
{%else%}
<tr><td>{{source_line.0}}</td>
<td> {{source_line.1}}</td></tr>
{%endifequal%}
{%endfor%}
</table>
{% for source_line in source_lines %}{% ifequal source_line.0 line %}<span class="error">{{source_line.0|rjust:"5"}}:{{ source_line.1}} </span>
{% else %}{{source_line.0|rjust:"5"}}:{{ source_line.1 }}
{% endifequal %}{% endfor %}
</pre>
{%ifnotequal bottom total%} {%ifnotequal bottom total%}
... ...
{%endifnotequal%} {%endifnotequal%}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -77,9 +77,6 @@ ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01
#What to report as the origin of templates that come from non loader sources (ie strings) #What to report as the origin of templates that come from non loader sources (ie strings)
UNKNOWN_SOURCE="<unknown source>" UNKNOWN_SOURCE="<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 # 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), 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))) re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END)))
@ -242,31 +239,20 @@ class DebugLexer(Lexer):
def __init__(self, template_string, origin): def __init__(self, template_string, origin):
super(DebugLexer,self).__init__(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): def tokenize(self):
"Return a list of tokens from a given template_string" "Return a list of tokens from a given template_string"
token_tups, upto = [], 0 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): for match in tag_re.finditer(self.template_string):
while next_linebreak <= upto:
line, next_linebreak = lines.next()
start, end = match.span() start, end = match.span()
if start > upto: if start > upto:
token_tups.append( (self.template_string[upto:start], line) ) token_tups.append( (self.template_string[upto:start], (upto, start) ) )
upto = start upto = start
while next_linebreak <= upto: token_tups.append( (self.template_string[start:end], (start,end) ) )
line, next_linebreak = lines.next()
token_tups.append( (self.template_string[start:end], line) )
upto = end upto = end
last_bit = self.template_string[upto:] last_bit = self.template_string[upto:]
if last_bit: if last_bit:
token_tups.append( (last_bit, line) ) token_tups.append( (last_bit, (upto, upto + len(last_bit) ) ) )
return [ self.create_token(tok, (self.origin, line)) for tok, line in token_tups] return [ self.create_token(tok, (self.origin, loc)) for tok, loc in token_tups]
def create_token(self, token_string, source): def create_token(self, token_string, source):
token = super(DebugLexer, self).create_token(token_string) token = super(DebugLexer, self).create_token(token_string)
@ -298,7 +284,7 @@ class Parser(object):
except IndexError: except IndexError:
self.empty_block_tag(token) self.empty_block_tag(token)
# execute callback function for this tag and append resulting node # execute callback function for this tag and append resulting node
self.enter_command(command, token); self.enter_command(command, token)
try: try:
compile_func = registered_tags[command] compile_func = registered_tags[command]
except KeyError: except KeyError:
@ -309,9 +295,9 @@ class Parser(object):
if not self.compile_function_error(token, e): if not self.compile_function_error(token, e):
raise raise
self.extend_nodelist(nodelist, compiled_result, token) self.extend_nodelist(nodelist, compiled_result, token)
self.exit_command(); self.exit_command()
if parse_until: if parse_until:
self.unclosed_block_tag(token, parse_until) self.unclosed_block_tag(parse_until)
return nodelist return nodelist
def create_variable_node(self, contents): def create_variable_node(self, contents):
@ -329,17 +315,20 @@ class Parser(object):
def exit_command(self): def exit_command(self):
pass pass
def error(self, token, msg ):
return TemplateSyntaxError(msg)
def empty_variable(self, token): def empty_variable(self, token):
raise TemplateSyntaxError, "Empty variable tag" raise self.error( token, "Empty variable tag")
def empty_block_tag(self, token): 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): 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): def unclosed_block_tag(self, parse_until):
raise TemplateSyntaxError, "Unclosed tags: %s " % ', '.join(parse_until) raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until))
def compile_function_error(self, token, e): def compile_function_error(self, token, e):
pass pass
@ -352,9 +341,6 @@ class Parser(object):
def delete_first_token(self): def delete_first_token(self):
del self.tokens[0] del self.tokens[0]
def format_source(source):
return "at %s, line %d" % source
class DebugParser(Parser): class DebugParser(Parser):
def __init__(self, lexer): def __init__(self, lexer):
@ -367,14 +353,14 @@ class DebugParser(Parser):
def exit_command(self): def exit_command(self):
self.command_stack.pop() 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 = TemplateSyntaxError(msg)
e.source = source e.source = source
return e return e
def format_source(self, source):
return "at %s, line %d" % source
def create_nodelist(self): def create_nodelist(self):
return DebugNodeList() return DebugNodeList()
@ -384,21 +370,12 @@ class DebugParser(Parser):
def extend_nodelist(self, nodelist, node, token): def extend_nodelist(self, nodelist, node, token):
node.source = token.source node.source = token.source
super(DebugParser, self).extend_nodelist(nodelist, node, token) 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): def unclosed_block_tag(self, parse_until):
raise self.error( token.source, "Empty block tag %s" % format_source(token.source)) (command, source) = self.command_stack.pop()
msg = "Unclosed tag '%s'. Looking for one of: %s " % \
def invalid_block_tag(self, token, command): (command, ', '.join(parse_until) )
raise self.error( token.source, "Invalid block tag: '%s' %s" % (command, format_source(token.source))) raise self.source_error( source, msg)
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 compile_function_error(self, token, e): def compile_function_error(self, token, e):
if not hasattr(e, 'source'): if not hasattr(e, 'source'):
@ -843,14 +820,14 @@ class DebugNodeList(NodeList):
e.source = node.source e.source = node.source
raise raise
except Exception, e: except Exception, e:
from traceback import extract_tb, format_list, format_exception_only from traceback import extract_tb, format_list, format_exception_only
from sys import exc_info from sys import exc_info
t,v,tb = exc_info() t,v,tb = exc_info()
frames = extract_tb(tb) frames = extract_tb(tb)
frames.pop(0) frames.pop(0)
wrapped = TemplateSyntaxError( ' While rendering %s , caught exception:\n %s' wrapped = TemplateSyntaxError( 'Caught exception:\n %s'
% ( format_source(node.source), % "".join(format_exception_only(t,v)).replace('\n',''))
"".join(format_exception_only(t,v)).replace('\n','')))
wrapped.source = node.source wrapped.source = node.source
wrapped.traceback = "".join(format_list(frames)) wrapped.traceback = "".join(format_list(frames))
raise wrapped raise wrapped

View File

@ -44,10 +44,10 @@ for path in TEMPLATE_LOADERS:
class LoaderOrigin(Origin): class LoaderOrigin(Origin):
def __init__(self, display_name, loader, name, dirs): def __init__(self, display_name, loader, name, dirs):
super(LoaderOrigin, self).__init__(display_name) 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): 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): def make_origin(display_name, loader, name, dirs):
if TEMPLATE_DEBUG: if TEMPLATE_DEBUG:
@ -99,7 +99,7 @@ def render_to_string(template_name, dictionary=None, context_instance=None):
if context_instance: if context_instance:
context_instance.update(dictionary) context_instance.update(dictionary)
else: else:
context_instance = Context(dictionary) context_instance = Context(dictionary)
return t.render(context_instance) return t.render(context_instance)
def select_template(template_name_list): def select_template(template_name_list):
@ -187,6 +187,8 @@ class ConstantIncludeNode(Node):
t = get_template(template_path) t = get_template(template_path)
self.nodelist = t.nodelist self.nodelist = t.nodelist
except Exception, e: except Exception, e:
if TEMPLATE_DEBUG:
raise
self.nodelist = None self.nodelist = None
def render(self, context): def render(self, context):
@ -202,10 +204,11 @@ class IncludeNode(Node):
def render(self, context): def render(self, context):
try: try:
template_path = resolve_variable(self.template_path_var, context) template_path = resolve_variable(self.template_path_var, context)
print "IncludeNode rendering %s" % template_path
t = get_template(template_path) t = get_template(template_path)
return t.render(context) return t.render(context)
except Exception, e: except Exception, e:
if TEMPLATE_DEBUG:
raise
return '' # Fail silently for invalid included templates. return '' # Fail silently for invalid included templates.
def do_block(parser, token): def do_block(parser, token):

View File

@ -1,30 +1,58 @@
class TemplateDebugMiddleware(object): 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): def process_exception(self, request, exception):
from django.core.template.loader import render_to_string from django.core.template.loader import render_to_string
from django.utils.html import escape from django.utils.html import escape
from django.utils.httpwrappers import HttpResponseServerError from django.utils.httpwrappers import HttpResponseServerError
from django.core.extensions import DjangoContext from django.core.extensions import DjangoContext
from itertools import count, izip from itertools import count, izip
context_lines = 10 context_lines = 10
if hasattr(exception, 'source'): if hasattr(exception, 'source'):
origin, line = exception.source origin, (start, end) = exception.source
template_source = origin.reload() 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) total = len(source_lines)
top = max(0, line - context_lines) top = max(0, line - context_lines)
bottom = min(total, line + 1 + context_lines) bottom = min(total, line + 1 + context_lines)
traceback = hasattr(exception, 'traceback') and exception.traceback or '' traceback = hasattr(exception, 'traceback') and exception.traceback or ''
return HttpResponseServerError( result = render_to_string('template_debug',
render_to_string('template_debug', DjangoContext(request, {
DjangoContext(request, { 'message' : exception.args[0],
'message' : exception.args[0], 'traceback' : traceback,
'traceback' : traceback, 'source_lines' : source_lines[top:bottom],
'source_lines' : source_lines[top:bottom], 'before' : before,
'top': top , 'during': during,
'bottom': bottom , 'after': after,
'total' : total, 'top': top ,
'line' : line 'bottom': bottom ,
}), 'total' : total,
) 'line' : line,
) 'name' : origin.name,
}),
)
return HttpResponseServerError(result)