mirror of
https://github.com/django/django.git
synced 2025-07-04 17:59:13 +00:00
Added TemplateDebugMiddleware. This intercepts TemplateSyntaxErrors that have been annotated with origin information and highlights where the problems are.
git-svn-id: http://code.djangoproject.com/svn/django/branches/new-admin@947 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
16d966a163
commit
8694d4b7b8
@ -74,7 +74,7 @@ class BaseHandler:
|
|||||||
response = middleware_method(request, e)
|
response = middleware_method(request, e)
|
||||||
if response:
|
if response:
|
||||||
return response
|
return response
|
||||||
raise e
|
raise
|
||||||
|
|
||||||
# Complain if the view returned None (a common error).
|
# Complain if the view returned None (a common error).
|
||||||
if response is None:
|
if response is None:
|
||||||
|
@ -107,10 +107,30 @@ class SilentVariableFailure(Exception):
|
|||||||
"Any function raising this exception will be ignored by resolve_variable"
|
"Any function raising this exception will be ignored by resolve_variable"
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class Origin(object):
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
raise NotImplementedException
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class StringOrigin(Origin):
|
||||||
|
def __init__(self, source):
|
||||||
|
super(StringOrigin, self).__init__(UNKNOWN_SOURCE)
|
||||||
|
self.source = source
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
return (self.source, self.name)
|
||||||
|
|
||||||
class Template:
|
class Template:
|
||||||
def __init__(self, template_string, filename=UNKNOWN_SOURCE):
|
def __init__(self, template_string, origin=None):
|
||||||
"Compilation stage"
|
"Compilation stage"
|
||||||
self.nodelist = compile_string(template_string, filename)
|
if origin == None:
|
||||||
|
origin = StringOrigin(template_string)
|
||||||
|
self.nodelist = compile_string(template_string, origin)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for node in self.nodelist:
|
for node in self.nodelist:
|
||||||
@ -120,8 +140,9 @@ class Template:
|
|||||||
def render(self, context):
|
def render(self, context):
|
||||||
"Display stage -- can be called many times"
|
"Display stage -- can be called many times"
|
||||||
return self.nodelist.render(context)
|
return self.nodelist.render(context)
|
||||||
|
|
||||||
|
|
||||||
def compile_string(template_string, filename):
|
def compile_string(template_string, origin):
|
||||||
"Compiles template_string into NodeList ready for rendering"
|
"Compiles template_string into NodeList ready for rendering"
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
lexer_factory = DebugLexer
|
lexer_factory = DebugLexer
|
||||||
@ -130,7 +151,7 @@ def compile_string(template_string, filename):
|
|||||||
lexer_factory = Lexer
|
lexer_factory = Lexer
|
||||||
parser_factory = Parser
|
parser_factory = Parser
|
||||||
|
|
||||||
lexer = lexer_factory(template_string, filename)
|
lexer = lexer_factory(template_string, origin)
|
||||||
parser = parser_factory(lexer.tokenize())
|
parser = parser_factory(lexer.tokenize())
|
||||||
return parser.parse()
|
return parser.parse()
|
||||||
|
|
||||||
@ -198,9 +219,9 @@ class Token:
|
|||||||
)
|
)
|
||||||
|
|
||||||
class Lexer(object):
|
class Lexer(object):
|
||||||
def __init__(self, template_string, filename):
|
def __init__(self, template_string, origin):
|
||||||
self.template_string = template_string
|
self.template_string = template_string
|
||||||
self.filename = filename
|
self.origin = origin
|
||||||
|
|
||||||
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"
|
||||||
@ -218,8 +239,8 @@ class Lexer(object):
|
|||||||
return token
|
return token
|
||||||
|
|
||||||
class DebugLexer(Lexer):
|
class DebugLexer(Lexer):
|
||||||
def __init__(self, template_string, filename):
|
def __init__(self, template_string, origin):
|
||||||
super(DebugLexer,self).__init__(template_string, filename)
|
super(DebugLexer,self).__init__(template_string, origin)
|
||||||
|
|
||||||
def find_linebreaks(self, template_string):
|
def find_linebreaks(self, template_string):
|
||||||
for match in newline_re.finditer(template_string):
|
for match in newline_re.finditer(template_string):
|
||||||
@ -233,19 +254,19 @@ class DebugLexer(Lexer):
|
|||||||
line, next_linebreak = lines.next()
|
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:
|
while next_linebreak <= upto:
|
||||||
line, next_linebreak = lines.next()
|
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], line) )
|
||||||
upto = start
|
upto = start
|
||||||
while next_linebreak <= upto:
|
while next_linebreak <= upto:
|
||||||
line, next_linebreak = lines.next()
|
line, next_linebreak = lines.next()
|
||||||
token_tups.append( (self.template_string[start:end], line) )
|
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, line) )
|
||||||
return [ self.create_token(tok, (self.filename, line)) for tok, line in token_tups]
|
return [ self.create_token(tok, (self.origin, line)) for tok, line 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)
|
||||||
@ -256,9 +277,8 @@ class Parser(object):
|
|||||||
def __init__(self, tokens):
|
def __init__(self, tokens):
|
||||||
self.tokens = tokens
|
self.tokens = tokens
|
||||||
|
|
||||||
|
|
||||||
def parse(self, parse_until=[]):
|
def parse(self, parse_until=[]):
|
||||||
nodelist = NodeList()
|
nodelist = self.create_nodelist()
|
||||||
while self.tokens:
|
while self.tokens:
|
||||||
token = self.next_token()
|
token = self.next_token()
|
||||||
if token.token_type == TOKEN_TEXT:
|
if token.token_type == TOKEN_TEXT:
|
||||||
@ -283,8 +303,14 @@ class Parser(object):
|
|||||||
compile_func = registered_tags[command]
|
compile_func = registered_tags[command]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.invalid_block_tag(token, command)
|
self.invalid_block_tag(token, command)
|
||||||
|
|
||||||
|
try:
|
||||||
|
compiled_result = compile_func(self, token)
|
||||||
|
except TemplateSyntaxError, e:
|
||||||
|
if not self.compile_function_error(token, e):
|
||||||
|
raise
|
||||||
|
|
||||||
self.extend_nodelist(nodelist, compile_func(self, token), token)
|
self.extend_nodelist(nodelist, compiled_result, token)
|
||||||
self.exit_command();
|
self.exit_command();
|
||||||
|
|
||||||
if parse_until:
|
if parse_until:
|
||||||
@ -292,6 +318,9 @@ class Parser(object):
|
|||||||
|
|
||||||
return nodelist
|
return nodelist
|
||||||
|
|
||||||
|
def create_nodelist(self):
|
||||||
|
return NodeList()
|
||||||
|
|
||||||
def extend_nodelist(self, nodelist, node, token):
|
def extend_nodelist(self, nodelist, node, token):
|
||||||
nodelist.append(node)
|
nodelist.append(node)
|
||||||
|
|
||||||
@ -312,6 +341,9 @@ class Parser(object):
|
|||||||
|
|
||||||
def unclosed_block_tag(self, token, parse_until):
|
def unclosed_block_tag(self, token, parse_until):
|
||||||
raise TemplateSyntaxError, "Unclosed tags: %s " % ', '.join(parse_until)
|
raise TemplateSyntaxError, "Unclosed tags: %s " % ', '.join(parse_until)
|
||||||
|
|
||||||
|
def compile_function_error(self, token, e):
|
||||||
|
pass
|
||||||
|
|
||||||
def next_token(self):
|
def next_token(self):
|
||||||
return self.tokens.pop(0)
|
return self.tokens.pop(0)
|
||||||
@ -334,27 +366,39 @@ class DebugParser(Parser):
|
|||||||
def exit_command(self):
|
def exit_command(self):
|
||||||
self.command_stack.pop()
|
self.command_stack.pop()
|
||||||
|
|
||||||
|
def error(self, source, msg):
|
||||||
|
e = TemplateSyntaxError(msg)
|
||||||
|
e.source = source
|
||||||
|
return e
|
||||||
|
|
||||||
def format_source(self, source):
|
def format_source(self, source):
|
||||||
return "at %s, line %d" % source
|
return "at %s, line %d" % source
|
||||||
|
|
||||||
|
def create_nodelist(self):
|
||||||
|
return DebugNodeList()
|
||||||
|
|
||||||
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):
|
def empty_variable(self, token):
|
||||||
raise TemplateSyntaxError, "Empty variable tag %s" % self.format_source(token.source)
|
raise self.error( token.source, "Empty variable tag %s" % self.format_source(token.source))
|
||||||
|
|
||||||
def empty_block_tag(self, token):
|
def empty_block_tag(self, token):
|
||||||
raise TemplateSyntaxError, "Empty block tag %s" % self.format_source(token.source)
|
raise self.error( token.source, "Empty block tag %s" % self.format_source(token.source))
|
||||||
|
|
||||||
def invalid_block_tag(self, token, command):
|
def invalid_block_tag(self, token, command):
|
||||||
raise TemplateSyntaxError, "Invalid block tag: '%s' %s" % (command, self.format_source(token.source))
|
raise self.error( token.source, "Invalid block tag: '%s' %s" % (command, self.format_source(token.source)))
|
||||||
|
|
||||||
def unclosed_block_tag(self, token, parse_until):
|
def unclosed_block_tag(self, token, parse_until):
|
||||||
(command, (file,line)) = self.command_stack.pop()
|
(command, (origin,line)) = self.command_stack.pop()
|
||||||
msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \
|
msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \
|
||||||
(command, file, line, ', '.join(parse_until) )
|
(command, origin, line, ', '.join(parse_until) )
|
||||||
raise TemplateSyntaxError, msg
|
raise self.error( token.source, msg)
|
||||||
|
|
||||||
|
def compile_function_error(self, token, e):
|
||||||
|
if not hasattr(e, 'source'):
|
||||||
|
e.source = token.source
|
||||||
|
|
||||||
class FilterParser:
|
class FilterParser:
|
||||||
"""Parse a variable token and its optional filters (all as a single string),
|
"""Parse a variable token and its optional filters (all as a single string),
|
||||||
@ -562,7 +606,12 @@ class NodeList(list):
|
|||||||
bits = []
|
bits = []
|
||||||
for node in self:
|
for node in self:
|
||||||
if isinstance(node, Node):
|
if isinstance(node, Node):
|
||||||
bits.append(node.render(context))
|
try:
|
||||||
|
result = node.render(context)
|
||||||
|
except TemplateSyntaxError, e:
|
||||||
|
if not self.handle_render_error(node, e):
|
||||||
|
raise
|
||||||
|
bits.append(result)
|
||||||
else:
|
else:
|
||||||
bits.append(node)
|
bits.append(node)
|
||||||
return ''.join(bits)
|
return ''.join(bits)
|
||||||
@ -574,6 +623,15 @@ class NodeList(list):
|
|||||||
nodes.extend(node.get_nodes_by_type(nodetype))
|
nodes.extend(node.get_nodes_by_type(nodetype))
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
def handle_render_error(self, node, exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class DebugNodeList(NodeList):
|
||||||
|
def handle_render_error(self, node, exception):
|
||||||
|
if not hasattr(exception, 'source'):
|
||||||
|
exception.source = node.source
|
||||||
|
|
||||||
|
|
||||||
class TextNode(Node):
|
class TextNode(Node):
|
||||||
def __init__(self, s):
|
def __init__(self, s):
|
||||||
self.s = s
|
self.s = s
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
# installed, because pkg_resources is necessary to read eggs.
|
# installed, because pkg_resources is necessary to read eggs.
|
||||||
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.template import Template, Context, Node, TemplateDoesNotExist, TemplateSyntaxError, resolve_variable_with_filters, register_tag, UNKNOWN_SOURCE
|
from django.core.template import Origin, StringOrigin, Template, Context, Node, TemplateDoesNotExist, TemplateSyntaxError, resolve_variable_with_filters, register_tag
|
||||||
from django.conf.settings import TEMPLATE_LOADERS
|
from django.conf.settings import TEMPLATE_LOADERS
|
||||||
|
|
||||||
template_source_loaders = []
|
template_source_loaders = []
|
||||||
@ -41,10 +41,30 @@ for path in TEMPLATE_LOADERS:
|
|||||||
else:
|
else:
|
||||||
template_source_loaders.append(func)
|
template_source_loaders.append(func)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class LoaderOrigin(Origin):
|
||||||
|
def __init__(self, name, loader):
|
||||||
|
super(LoaderOrigin, self).__init__(name)
|
||||||
|
self.loader = loader
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
if self.loader:
|
||||||
|
return self.loader()
|
||||||
|
else:
|
||||||
|
raise NotImplementedException
|
||||||
|
|
||||||
|
|
||||||
def find_template_source(name, dirs=None):
|
def find_template_source(name, dirs=None):
|
||||||
for loader in template_source_loaders:
|
for loader in template_source_loaders:
|
||||||
try:
|
try:
|
||||||
return loader(name, dirs)
|
source, display_name = loader(name, dirs)
|
||||||
|
|
||||||
|
def reload():
|
||||||
|
return loader(name, dirs)[0]
|
||||||
|
|
||||||
|
return (source, LoaderOrigin(display_name, reload))
|
||||||
except TemplateDoesNotExist:
|
except TemplateDoesNotExist:
|
||||||
pass
|
pass
|
||||||
raise TemplateDoesNotExist, name
|
raise TemplateDoesNotExist, name
|
||||||
@ -62,12 +82,15 @@ def get_template(template_name):
|
|||||||
"""
|
"""
|
||||||
return get_template_from_string(*find_template_source(template_name))
|
return get_template_from_string(*find_template_source(template_name))
|
||||||
|
|
||||||
def get_template_from_string(source, filename=UNKNOWN_SOURCE):
|
def get_template_from_string(source, origin=None ):
|
||||||
"""
|
"""
|
||||||
Returns a compiled Template object for the given template code,
|
Returns a compiled Template object for the given template code,
|
||||||
handling template inheritance recursively.
|
handling template inheritance recursively.
|
||||||
"""
|
"""
|
||||||
return Template(source, filename)
|
if origin==None:
|
||||||
|
#Could do some crazy stack frame stuff to record where this string came from.
|
||||||
|
origin = StringOrigin(source)
|
||||||
|
return Template(source, origin)
|
||||||
|
|
||||||
def render_to_string(template_name, dictionary=None, context_instance=None):
|
def render_to_string(template_name, dictionary=None, context_instance=None):
|
||||||
"""
|
"""
|
||||||
|
25
django/middleware/template_debug.py
Normal file
25
django/middleware/template_debug.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from django.core.extensions import render_to_response
|
||||||
|
from django.utils.html import escape
|
||||||
|
|
||||||
|
context_lines = 10
|
||||||
|
|
||||||
|
class TemplateDebugMiddleware(object):
|
||||||
|
def process_exception(self, request, exception):
|
||||||
|
if hasattr(exception, 'source'):
|
||||||
|
origin, line = exception.source
|
||||||
|
template_source = origin.reload()
|
||||||
|
|
||||||
|
source_lines = [ (i + 1,s) for (i,s) in enumerate(escape(template_source).split("\n"))]
|
||||||
|
total = len(source_lines)
|
||||||
|
top = max(0, line - context_lines)
|
||||||
|
bottom = min(total, line + 1 + context_lines)
|
||||||
|
|
||||||
|
return render_to_response('template_debug', {
|
||||||
|
'message' : exception.args[0],
|
||||||
|
'source_lines' : source_lines[top:bottom],
|
||||||
|
'top': top ,
|
||||||
|
'bottom': bottom ,
|
||||||
|
'total' : total,
|
||||||
|
'line' : line
|
||||||
|
})
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user