Fixed #603 -- Added template debugging errors to pretty error-page output, if TEMPLATE_DEBUG setting is True. Also refactored FilterParser for a significant speed increase and changed the template_loader interface so that it returns information about the loader. Taken from new-admin. Thanks rjwittams and crew
git-svn-id: http://code.djangoproject.com/svn/django/trunk@1379 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
cfc5786d03
commit
5d863f1fbd
|
@ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
####################
|
####################
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
TEMPLATE_DEBUG = False
|
||||||
|
|
||||||
# Whether to use the "Etag" header. This saves bandwidth but slows down performance.
|
# Whether to use the "Etag" header. This saves bandwidth but slows down performance.
|
||||||
USE_ETAGS = False
|
USE_ETAGS = False
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# Django settings for {{ project_name }} project.
|
# Django settings for {{ project_name }} project.
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
TEMPLATE_DEBUG = DEBUG
|
||||||
|
|
||||||
ADMINS = (
|
ADMINS = (
|
||||||
# ('Your Name', 'your_email@domain.com'),
|
# ('Your Name', 'your_email@domain.com'),
|
||||||
|
|
|
@ -55,7 +55,7 @@ times with multiple contexts)
|
||||||
'\n<html>\n\n</html>\n'
|
'\n<html>\n\n</html>\n'
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
from django.conf.settings import DEFAULT_CHARSET
|
from django.conf.settings import DEFAULT_CHARSET, TEMPLATE_DEBUG
|
||||||
|
|
||||||
__all__ = ('Template','Context','compile_string')
|
__all__ = ('Template','Context','compile_string')
|
||||||
|
|
||||||
|
@ -74,6 +74,10 @@ VARIABLE_TAG_END = '}}'
|
||||||
|
|
||||||
ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
|
ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
|
||||||
|
|
||||||
|
# what to report as the origin for templates that come from non-loader sources
|
||||||
|
# (e.g. strings)
|
||||||
|
UNKNOWN_SOURCE="<unknown source>"
|
||||||
|
|
||||||
# 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)))
|
||||||
|
@ -101,10 +105,32 @@ 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
|
||||||
|
|
||||||
class Template:
|
class Template:
|
||||||
def __init__(self, template_string):
|
def __init__(self, template_string, origin=None):
|
||||||
"Compilation stage"
|
"Compilation stage"
|
||||||
self.nodelist = compile_string(template_string)
|
if TEMPLATE_DEBUG and origin == None:
|
||||||
|
origin = StringOrigin(template_string)
|
||||||
|
# Could do some crazy stack-frame stuff to record where this string
|
||||||
|
# came from...
|
||||||
|
self.nodelist = compile_string(template_string, origin)
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for node in self.nodelist:
|
for node in self.nodelist:
|
||||||
|
@ -115,10 +141,10 @@ class Template:
|
||||||
"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):
|
def compile_string(template_string, origin):
|
||||||
"Compiles template_string into NodeList ready for rendering"
|
"Compiles template_string into NodeList ready for rendering"
|
||||||
tokens = tokenize(template_string)
|
lexer = lexer_factory(template_string, origin)
|
||||||
parser = Parser(tokens)
|
parser = parser_factory(lexer.tokenize())
|
||||||
return parser.parse()
|
return parser.parse()
|
||||||
|
|
||||||
class Context:
|
class Context:
|
||||||
|
@ -163,6 +189,12 @@ class Context:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get(self, key, otherwise):
|
||||||
|
for dict in self.dicts:
|
||||||
|
if dict.has_key(key):
|
||||||
|
return dict[key]
|
||||||
|
return otherwise
|
||||||
|
|
||||||
def update(self, other_dict):
|
def update(self, other_dict):
|
||||||
"Like dict.update(). Pushes an entire dictionary's keys and values onto the context."
|
"Like dict.update(). Pushes an entire dictionary's keys and values onto the context."
|
||||||
self.dicts = [other_dict] + self.dicts
|
self.dicts = [other_dict] + self.dicts
|
||||||
|
@ -174,39 +206,76 @@ class Token:
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '<%s token: "%s...">' % (
|
return '<%s token: "%s...">' % (
|
||||||
{TOKEN_TEXT:'Text', TOKEN_VAR:'Var', TOKEN_BLOCK:'Block'}[self.token_type],
|
{TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block'}[self.token_type],
|
||||||
self.contents[:20].replace('\n', '')
|
self.contents[:20].replace('\n', '')
|
||||||
)
|
)
|
||||||
|
|
||||||
def tokenize(template_string):
|
def __repr__(self):
|
||||||
"Return a list of tokens from a given template_string"
|
return '<%s token: "%s">' % (
|
||||||
# remove all empty strings, because the regex has a tendency to add them
|
{TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block'}[self.token_type],
|
||||||
bits = filter(None, tag_re.split(template_string))
|
self.contents[:].replace('\n', '')
|
||||||
return map(create_token, bits)
|
)
|
||||||
|
|
||||||
def create_token(token_string):
|
class Lexer(object):
|
||||||
"Convert the given token string into a new Token object and return it"
|
def __init__(self, template_string, origin):
|
||||||
if token_string.startswith(VARIABLE_TAG_START):
|
self.template_string = template_string
|
||||||
return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
|
self.origin = origin
|
||||||
elif token_string.startswith(BLOCK_TAG_START):
|
|
||||||
return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
|
|
||||||
else:
|
|
||||||
return Token(TOKEN_TEXT, token_string)
|
|
||||||
|
|
||||||
class Parser:
|
def tokenize(self):
|
||||||
|
"Return a list of tokens from a given template_string"
|
||||||
|
# remove all empty strings, because the regex has a tendency to add them
|
||||||
|
bits = filter(None, tag_re.split(self.template_string))
|
||||||
|
return map(self.create_token, bits)
|
||||||
|
|
||||||
|
def create_token(self,token_string):
|
||||||
|
"Convert the given token string into a new Token object and return it"
|
||||||
|
if token_string.startswith(VARIABLE_TAG_START):
|
||||||
|
token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip())
|
||||||
|
elif token_string.startswith(BLOCK_TAG_START):
|
||||||
|
token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip())
|
||||||
|
else:
|
||||||
|
token = Token(TOKEN_TEXT, token_string)
|
||||||
|
return token
|
||||||
|
|
||||||
|
class DebugLexer(Lexer):
|
||||||
|
def __init__(self, template_string, origin):
|
||||||
|
super(DebugLexer, self).__init__(template_string, origin)
|
||||||
|
|
||||||
|
def tokenize(self):
|
||||||
|
"Return a list of tokens from a given template_string"
|
||||||
|
token_tups, upto = [], 0
|
||||||
|
for match in tag_re.finditer(self.template_string):
|
||||||
|
start, end = match.span()
|
||||||
|
if start > upto:
|
||||||
|
token_tups.append( (self.template_string[upto:start], (upto, start)) )
|
||||||
|
upto = start
|
||||||
|
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, (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)
|
||||||
|
token.source = source
|
||||||
|
return token
|
||||||
|
|
||||||
|
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:
|
||||||
nodelist.append(TextNode(token.contents))
|
self.extend_nodelist(nodelist, TextNode(token.contents), token)
|
||||||
elif token.token_type == TOKEN_VAR:
|
elif token.token_type == TOKEN_VAR:
|
||||||
if not token.contents:
|
if not token.contents:
|
||||||
raise TemplateSyntaxError, "Empty variable tag"
|
self.empty_variable(token)
|
||||||
nodelist.append(VariableNode(token.contents))
|
var_node = self.create_variable_node(token.contents)
|
||||||
|
self.extend_nodelist(nodelist, var_node,token)
|
||||||
elif token.token_type == TOKEN_BLOCK:
|
elif token.token_type == TOKEN_BLOCK:
|
||||||
if token.contents in parse_until:
|
if token.contents in parse_until:
|
||||||
# put token back on token list so calling code knows why it terminated
|
# put token back on token list so calling code knows why it terminated
|
||||||
|
@ -215,16 +284,57 @@ class Parser:
|
||||||
try:
|
try:
|
||||||
command = token.contents.split()[0]
|
command = token.contents.split()[0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
raise TemplateSyntaxError, "Empty block tag"
|
self.empty_block_tag(token)
|
||||||
|
# execute callback function for this tag and append resulting node
|
||||||
|
self.enter_command(command, token)
|
||||||
try:
|
try:
|
||||||
# execute callback function for this tag and append resulting node
|
compile_func = registered_tags[command]
|
||||||
nodelist.append(registered_tags[command](self, token))
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise TemplateSyntaxError, "Invalid block tag: '%s'" % 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, compiled_result, token)
|
||||||
|
self.exit_command()
|
||||||
if parse_until:
|
if parse_until:
|
||||||
raise TemplateSyntaxError, "Unclosed tag(s): '%s'" % ', '.join(parse_until)
|
self.unclosed_block_tag(parse_until)
|
||||||
return nodelist
|
return nodelist
|
||||||
|
|
||||||
|
def create_variable_node(self, contents):
|
||||||
|
return VariableNode(contents)
|
||||||
|
|
||||||
|
def create_nodelist(self):
|
||||||
|
return NodeList()
|
||||||
|
|
||||||
|
def extend_nodelist(self, nodelist, node, token):
|
||||||
|
nodelist.append(node)
|
||||||
|
|
||||||
|
def enter_command(self, command, token):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def exit_command(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def error(self, token, msg ):
|
||||||
|
return TemplateSyntaxError(msg)
|
||||||
|
|
||||||
|
def empty_variable(self, token):
|
||||||
|
raise self.error( token, "Empty variable tag")
|
||||||
|
|
||||||
|
def empty_block_tag(self, token):
|
||||||
|
raise self.error( token, "Empty block tag")
|
||||||
|
|
||||||
|
def invalid_block_tag(self, token, command):
|
||||||
|
raise self.error( token, "Invalid block tag: '%s'" % command)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
def next_token(self):
|
def next_token(self):
|
||||||
return self.tokens.pop(0)
|
return self.tokens.pop(0)
|
||||||
|
|
||||||
|
@ -234,6 +344,51 @@ class Parser:
|
||||||
def delete_first_token(self):
|
def delete_first_token(self):
|
||||||
del self.tokens[0]
|
del self.tokens[0]
|
||||||
|
|
||||||
|
class DebugParser(Parser):
|
||||||
|
def __init__(self, lexer):
|
||||||
|
super(DebugParser, self).__init__(lexer)
|
||||||
|
self.command_stack = []
|
||||||
|
|
||||||
|
def enter_command(self, command, token):
|
||||||
|
self.command_stack.append( (command, token.source) )
|
||||||
|
|
||||||
|
def exit_command(self):
|
||||||
|
self.command_stack.pop()
|
||||||
|
|
||||||
|
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 create_nodelist(self):
|
||||||
|
return DebugNodeList()
|
||||||
|
|
||||||
|
def create_variable_node(self, contents):
|
||||||
|
return DebugVariableNode(contents)
|
||||||
|
|
||||||
|
def extend_nodelist(self, nodelist, node, token):
|
||||||
|
node.source = token.source
|
||||||
|
super(DebugParser, self).extend_nodelist(nodelist, node, token)
|
||||||
|
|
||||||
|
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'):
|
||||||
|
e.source = token.source
|
||||||
|
|
||||||
|
if TEMPLATE_DEBUG:
|
||||||
|
lexer_factory = DebugLexer
|
||||||
|
parser_factory = DebugParser
|
||||||
|
else:
|
||||||
|
lexer_factory = Lexer
|
||||||
|
parser_factory = Parser
|
||||||
|
|
||||||
class TokenParser:
|
class TokenParser:
|
||||||
"""
|
"""
|
||||||
Subclass this and implement the top() method to parse a template line. When
|
Subclass this and implement the top() method to parse a template line. When
|
||||||
|
@ -316,7 +471,34 @@ class TokenParser:
|
||||||
self.pointer = i
|
self.pointer = i
|
||||||
return s
|
return s
|
||||||
|
|
||||||
class FilterParser:
|
|
||||||
|
|
||||||
|
|
||||||
|
filter_raw_string = r"""
|
||||||
|
^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s|
|
||||||
|
^"(?P<constant>%(str)s)"|
|
||||||
|
^(?P<var>[%(var_chars)s]+)|
|
||||||
|
(?:%(filter_sep)s
|
||||||
|
(?P<filter_name>\w+)
|
||||||
|
(?:%(arg_sep)s
|
||||||
|
(?:
|
||||||
|
%(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
|
||||||
|
"(?P<arg>%(str)s)"
|
||||||
|
)
|
||||||
|
)?
|
||||||
|
)""" % {
|
||||||
|
'str': r"""[^"\\]*(?:\\.[^"\\]*)*""",
|
||||||
|
'var_chars': "A-Za-z0-9\_\." ,
|
||||||
|
'filter_sep': re.escape(FILTER_SEPARATOR),
|
||||||
|
'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
|
||||||
|
'i18n_open' : re.escape("_("),
|
||||||
|
'i18n_close' : re.escape(")"),
|
||||||
|
}
|
||||||
|
|
||||||
|
filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "")
|
||||||
|
filter_re = re.compile(filter_raw_string)
|
||||||
|
|
||||||
|
class FilterParser(object):
|
||||||
"""
|
"""
|
||||||
Parses a variable token and its optional filters (all as a single string),
|
Parses a variable token and its optional filters (all as a single string),
|
||||||
and return a list of tuples of the filter name and arguments.
|
and return a list of tuples of the filter name and arguments.
|
||||||
|
@ -331,162 +513,45 @@ class FilterParser:
|
||||||
This class should never be instantiated outside of the
|
This class should never be instantiated outside of the
|
||||||
get_filters_from_token helper function.
|
get_filters_from_token helper function.
|
||||||
"""
|
"""
|
||||||
def __init__(self, s):
|
def __init__(self, token):
|
||||||
self.s = s
|
matches = filter_re.finditer(token)
|
||||||
self.i = -1
|
var = None
|
||||||
self.current = ''
|
filters = []
|
||||||
self.filters = []
|
upto = 0
|
||||||
self.current_filter_name = None
|
for match in matches:
|
||||||
self.current_filter_arg = None
|
start = match.start()
|
||||||
# First read the variable part. Decide whether we need to parse a
|
if upto != start:
|
||||||
# string or a variable by peeking into the stream.
|
raise TemplateSyntaxError, "Could not parse some characters: %s|%s|%s" % \
|
||||||
if self.peek_char() in ('_', '"', "'"):
|
(token[:upto], token[upto:start], token[start:])
|
||||||
self.var = self.read_constant_string_token()
|
if var == None:
|
||||||
else:
|
var, constant, i18n_constant = match.group("var", "constant", "i18n_constant")
|
||||||
self.var = self.read_alphanumeric_token()
|
if i18n_constant:
|
||||||
if not self.var:
|
var = '"%s"' % _(i18n_constant)
|
||||||
raise TemplateSyntaxError, "Could not read variable name: '%s'" % self.s
|
elif constant:
|
||||||
if self.var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or self.var[0] == '_':
|
var = '"%s"' % constant
|
||||||
raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % self.var
|
upto = match.end()
|
||||||
# Have we reached the end?
|
if var == None:
|
||||||
if self.current is None:
|
raise TemplateSyntaxError, "Could not find variable at start of %s" % token
|
||||||
return
|
elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_':
|
||||||
if self.current != FILTER_SEPARATOR:
|
raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % var
|
||||||
raise TemplateSyntaxError, "Bad character (expecting '%s') '%s'" % (FILTER_SEPARATOR, self.current)
|
else:
|
||||||
# We have a filter separator; start reading the filters
|
filter_name = match.group("filter_name")
|
||||||
self.read_filters()
|
arg, i18n_arg = match.group("arg","i18n_arg")
|
||||||
|
if i18n_arg:
|
||||||
def peek_char(self):
|
arg =_(i18n_arg.replace('\\', ''))
|
||||||
try:
|
if arg:
|
||||||
return self.s[self.i+1]
|
arg = arg.replace('\\', '')
|
||||||
except IndexError:
|
if not registered_filters.has_key(filter_name):
|
||||||
return None
|
raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name
|
||||||
|
if registered_filters[filter_name][1] == True and arg is None:
|
||||||
def next_char(self):
|
raise TemplateSyntaxError, "Filter '%s' requires an argument" % filter_name
|
||||||
self.i = self.i + 1
|
if registered_filters[filter_name][1] == False and arg is not None:
|
||||||
try:
|
raise TemplateSyntaxError, "Filter '%s' should not have an argument (argument is %r)" % (filter_name, arg)
|
||||||
self.current = self.s[self.i]
|
filters.append( (filter_name,arg) )
|
||||||
except IndexError:
|
upto = match.end()
|
||||||
self.current = None
|
if upto != len(token):
|
||||||
|
raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:]
|
||||||
def read_constant_string_token(self):
|
self.var , self.filters = var, filters
|
||||||
"""
|
|
||||||
Reads a constant string that must be delimited by either " or '
|
|
||||||
characters. The string is returned with its delimiters.
|
|
||||||
"""
|
|
||||||
val = ''
|
|
||||||
qchar = None
|
|
||||||
i18n = False
|
|
||||||
self.next_char()
|
|
||||||
if self.current == '_':
|
|
||||||
i18n = True
|
|
||||||
self.next_char()
|
|
||||||
if self.current != '(':
|
|
||||||
raise TemplateSyntaxError, "Bad character (expecting '(') '%s'" % self.current
|
|
||||||
self.next_char()
|
|
||||||
if not self.current in ('"', "'"):
|
|
||||||
raise TemplateSyntaxError, "Bad character (expecting '\"' or ''') '%s'" % self.current
|
|
||||||
qchar = self.current
|
|
||||||
val += qchar
|
|
||||||
while 1:
|
|
||||||
self.next_char()
|
|
||||||
if self.current == qchar:
|
|
||||||
break
|
|
||||||
val += self.current
|
|
||||||
val += self.current
|
|
||||||
self.next_char()
|
|
||||||
if i18n:
|
|
||||||
if self.current != ')':
|
|
||||||
raise TemplateSyntaxError, "Bad character (expecting ')') '%s'" % self.current
|
|
||||||
self.next_char()
|
|
||||||
val = qchar+_(val.strip(qchar))+qchar
|
|
||||||
return val
|
|
||||||
|
|
||||||
def read_alphanumeric_token(self):
|
|
||||||
"""
|
|
||||||
Reads a variable name or filter name, which are continuous strings of
|
|
||||||
alphanumeric characters + the underscore.
|
|
||||||
"""
|
|
||||||
var = ''
|
|
||||||
while 1:
|
|
||||||
self.next_char()
|
|
||||||
if self.current is None:
|
|
||||||
break
|
|
||||||
if self.current not in ALLOWED_VARIABLE_CHARS:
|
|
||||||
break
|
|
||||||
var += self.current
|
|
||||||
return var
|
|
||||||
|
|
||||||
def read_filters(self):
|
|
||||||
while 1:
|
|
||||||
filter_name, arg = self.read_filter()
|
|
||||||
if not registered_filters.has_key(filter_name):
|
|
||||||
raise TemplateSyntaxError, "Invalid filter: '%s'" % filter_name
|
|
||||||
if registered_filters[filter_name][1] == True and arg is None:
|
|
||||||
raise TemplateSyntaxError, "Filter '%s' requires an argument" % filter_name
|
|
||||||
if registered_filters[filter_name][1] == False and arg is not None:
|
|
||||||
raise TemplateSyntaxError, "Filter '%s' should not have an argument (argument is %r)" % (filter_name, arg)
|
|
||||||
self.filters.append((filter_name, arg))
|
|
||||||
if self.current is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
def read_filter(self):
|
|
||||||
self.current_filter_name = self.read_alphanumeric_token()
|
|
||||||
self.current_filter_arg = None
|
|
||||||
# Have we reached the end?
|
|
||||||
if self.current is None:
|
|
||||||
return (self.current_filter_name, None)
|
|
||||||
# Does the filter have an argument?
|
|
||||||
if self.current == FILTER_ARGUMENT_SEPARATOR:
|
|
||||||
self.current_filter_arg = self.read_arg()
|
|
||||||
return (self.current_filter_name, self.current_filter_arg)
|
|
||||||
# Next thing MUST be a pipe
|
|
||||||
if self.current != FILTER_SEPARATOR:
|
|
||||||
raise TemplateSyntaxError, "Bad character (expecting '%s') '%s'" % (FILTER_SEPARATOR, self.current)
|
|
||||||
return (self.current_filter_name, self.current_filter_arg)
|
|
||||||
|
|
||||||
def read_arg(self):
|
|
||||||
# First read a " or a _("
|
|
||||||
self.next_char()
|
|
||||||
translated = False
|
|
||||||
if self.current == '_':
|
|
||||||
self.next_char()
|
|
||||||
if self.current != '(':
|
|
||||||
raise TemplateSyntaxError, "Bad character (expecting '(') '%s'" % self.current
|
|
||||||
translated = True
|
|
||||||
self.next_char()
|
|
||||||
if self.current != '"':
|
|
||||||
raise TemplateSyntaxError, "Bad character (expecting '\"') '%s'" % self.current
|
|
||||||
self.escaped = False
|
|
||||||
arg = ''
|
|
||||||
while 1:
|
|
||||||
self.next_char()
|
|
||||||
if self.current == '"' and not self.escaped:
|
|
||||||
break
|
|
||||||
if self.current == '\\' and not self.escaped:
|
|
||||||
self.escaped = True
|
|
||||||
continue
|
|
||||||
if self.current == '\\' and self.escaped:
|
|
||||||
arg += '\\'
|
|
||||||
self.escaped = False
|
|
||||||
continue
|
|
||||||
if self.current == '"' and self.escaped:
|
|
||||||
arg += '"'
|
|
||||||
self.escaped = False
|
|
||||||
continue
|
|
||||||
if self.escaped and self.current not in '\\"':
|
|
||||||
raise TemplateSyntaxError, "Unescaped backslash in '%s'" % self.s
|
|
||||||
if self.current is None:
|
|
||||||
raise TemplateSyntaxError, "Unexpected end of argument in '%s'" % self.s
|
|
||||||
arg += self.current
|
|
||||||
# self.current must now be '"'
|
|
||||||
self.next_char()
|
|
||||||
if translated:
|
|
||||||
if self.current != ')':
|
|
||||||
raise TemplateSyntaxError, "Bad character (expecting ')') '%s'" % self.current
|
|
||||||
self.next_char()
|
|
||||||
arg = _(arg)
|
|
||||||
return arg
|
|
||||||
|
|
||||||
def get_filters_from_token(token):
|
def get_filters_from_token(token):
|
||||||
"Convenient wrapper for FilterParser"
|
"Convenient wrapper for FilterParser"
|
||||||
|
@ -580,7 +645,7 @@ 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))
|
bits.append(self.render_node(node, context))
|
||||||
else:
|
else:
|
||||||
bits.append(node)
|
bits.append(node)
|
||||||
return ''.join(bits)
|
return ''.join(bits)
|
||||||
|
@ -592,6 +657,25 @@ class NodeList(list):
|
||||||
nodes.extend(node.get_nodes_by_type(nodetype))
|
nodes.extend(node.get_nodes_by_type(nodetype))
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
def render_node(self, node, context):
|
||||||
|
return(node.render(context))
|
||||||
|
|
||||||
|
class DebugNodeList(NodeList):
|
||||||
|
def render_node(self, node, context):
|
||||||
|
try:
|
||||||
|
result = node.render(context)
|
||||||
|
except TemplateSyntaxError, e:
|
||||||
|
if not hasattr(e, 'source'):
|
||||||
|
e.source = node.source
|
||||||
|
raise
|
||||||
|
except Exception:
|
||||||
|
from sys import exc_info
|
||||||
|
wrapped = TemplateSyntaxError('Caught an exception while rendering.')
|
||||||
|
wrapped.source = node.source
|
||||||
|
wrapped.exc_info = exc_info()
|
||||||
|
raise wrapped
|
||||||
|
return result
|
||||||
|
|
||||||
class TextNode(Node):
|
class TextNode(Node):
|
||||||
def __init__(self, s):
|
def __init__(self, s):
|
||||||
self.s = s
|
self.s = s
|
||||||
|
@ -609,14 +693,28 @@ class VariableNode(Node):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Variable Node: %s>" % self.var_string
|
return "<Variable Node: %s>" % self.var_string
|
||||||
|
|
||||||
def render(self, context):
|
def encode_output(self, output):
|
||||||
output = resolve_variable_with_filters(self.var_string, context)
|
|
||||||
# Check type so that we don't run str() on a Unicode object
|
# Check type so that we don't run str() on a Unicode object
|
||||||
if not isinstance(output, basestring):
|
if not isinstance(output, basestring):
|
||||||
output = str(output)
|
return str(output)
|
||||||
elif isinstance(output, unicode):
|
elif isinstance(output, unicode):
|
||||||
output = output.encode(DEFAULT_CHARSET)
|
return output.encode(DEFAULT_CHARSET)
|
||||||
return output
|
else:
|
||||||
|
return output
|
||||||
|
|
||||||
|
def render(self, context):
|
||||||
|
output = resolve_variable_with_filters(self.var_string, context)
|
||||||
|
return self.encode_output(output)
|
||||||
|
|
||||||
|
class DebugVariableNode(VariableNode):
|
||||||
|
def render(self, context):
|
||||||
|
try:
|
||||||
|
output = resolve_variable_with_filters(self.var_string, context)
|
||||||
|
except TemplateSyntaxError, e:
|
||||||
|
if not hasattr(e, 'source'):
|
||||||
|
e.source = self.source
|
||||||
|
raise
|
||||||
|
return self.encode_output(output)
|
||||||
|
|
||||||
def register_tag(token_command, callback_function):
|
def register_tag(token_command, callback_function):
|
||||||
registered_tags[token_command] = callback_function
|
registered_tags[token_command] = callback_function
|
||||||
|
|
|
@ -192,6 +192,7 @@ class RegroupNode(Node):
|
||||||
for obj in obj_list:
|
for obj in obj_list:
|
||||||
grouper = resolve_variable_with_filters('var.%s' % self.expression, \
|
grouper = resolve_variable_with_filters('var.%s' % self.expression, \
|
||||||
Context({'var': obj}))
|
Context({'var': obj}))
|
||||||
|
# TODO: Is this a sensible way to determine equality?
|
||||||
if output and repr(output[-1]['grouper']) == repr(grouper):
|
if output and repr(output[-1]['grouper']) == repr(grouper):
|
||||||
output[-1]['list'].append(obj)
|
output[-1]['list'].append(obj)
|
||||||
else:
|
else:
|
||||||
|
@ -628,8 +629,8 @@ def do_load(parser, token):
|
||||||
# check at compile time that the module can be imported
|
# check at compile time that the module can be imported
|
||||||
try:
|
try:
|
||||||
LoadNode.load_taglib(taglib)
|
LoadNode.load_taglib(taglib)
|
||||||
except ImportError:
|
except ImportError, e:
|
||||||
raise TemplateSyntaxError, "'%s' is not a valid tag library" % taglib
|
raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e)
|
||||||
return LoadNode(taglib)
|
return LoadNode(taglib)
|
||||||
|
|
||||||
def do_now(parser, token):
|
def do_now(parser, token):
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
# name is the template name.
|
# name is the template name.
|
||||||
# dirs is an optional list of directories to search instead of TEMPLATE_DIRS.
|
# dirs is an optional list of directories to search instead of TEMPLATE_DIRS.
|
||||||
#
|
#
|
||||||
|
# The loader should return a tuple of (template_source, path). The path returned
|
||||||
|
# might be shown to the user for debugging purposes, so it should identify where
|
||||||
|
# the template was loaded from.
|
||||||
|
#
|
||||||
# Each loader should have an "is_usable" attribute set. This is a boolean that
|
# Each loader should have an "is_usable" attribute set. This is a boolean that
|
||||||
# specifies whether the loader can be used in this Python installation. Each
|
# specifies whether the loader can be used in this Python installation. Each
|
||||||
# loader is responsible for setting this when it's initialized.
|
# loader is responsible for setting this when it's initialized.
|
||||||
|
@ -17,8 +21,8 @@
|
||||||
# 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, resolve_variable, register_tag
|
from django.core.template import Origin, StringOrigin, Template, Context, Node, TemplateDoesNotExist, TemplateSyntaxError, resolve_variable_with_filters, resolve_variable, register_tag
|
||||||
from django.conf.settings import TEMPLATE_LOADERS
|
from django.conf.settings import TEMPLATE_LOADERS, TEMPLATE_DEBUG
|
||||||
|
|
||||||
template_source_loaders = []
|
template_source_loaders = []
|
||||||
for path in TEMPLATE_LOADERS:
|
for path in TEMPLATE_LOADERS:
|
||||||
|
@ -38,14 +42,32 @@ for path in TEMPLATE_LOADERS:
|
||||||
else:
|
else:
|
||||||
template_source_loaders.append(func)
|
template_source_loaders.append(func)
|
||||||
|
|
||||||
def load_template_source(name, dirs=None):
|
class LoaderOrigin(Origin):
|
||||||
|
def __init__(self, display_name, loader, name, dirs):
|
||||||
|
super(LoaderOrigin, self).__init__(display_name)
|
||||||
|
self.loader, self.loadname, self.dirs = loader, name, dirs
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
return self.loader(self.loadname, self.dirs)[0]
|
||||||
|
|
||||||
|
def make_origin(display_name, loader, name, dirs):
|
||||||
|
if TEMPLATE_DEBUG:
|
||||||
|
return LoaderOrigin(display_name, loader, name, dirs)
|
||||||
|
else:
|
||||||
|
return 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)
|
||||||
|
return (source, make_origin(display_name, loader, name, dirs))
|
||||||
except TemplateDoesNotExist:
|
except TemplateDoesNotExist:
|
||||||
pass
|
pass
|
||||||
raise TemplateDoesNotExist, name
|
raise TemplateDoesNotExist, name
|
||||||
|
|
||||||
|
def load_template_source(name, dirs=None):
|
||||||
|
find_template_source(name, dirs)[0]
|
||||||
|
|
||||||
class ExtendsError(Exception):
|
class ExtendsError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -54,14 +76,14 @@ def get_template(template_name):
|
||||||
Returns a compiled Template object for the given template name,
|
Returns a compiled Template object for the given template name,
|
||||||
handling template inheritance recursively.
|
handling template inheritance recursively.
|
||||||
"""
|
"""
|
||||||
return get_template_from_string(load_template_source(template_name))
|
return get_template_from_string(*find_template_source(template_name))
|
||||||
|
|
||||||
def get_template_from_string(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)
|
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):
|
||||||
"""
|
"""
|
||||||
|
@ -134,7 +156,7 @@ class ExtendsNode(Node):
|
||||||
error_msg += " Got this from the %r variable." % self.parent_name_var
|
error_msg += " Got this from the %r variable." % self.parent_name_var
|
||||||
raise TemplateSyntaxError, error_msg
|
raise TemplateSyntaxError, error_msg
|
||||||
try:
|
try:
|
||||||
return get_template_from_string(load_template_source(parent, self.template_dirs))
|
return get_template_from_string(*find_template_source(parent, self.template_dirs))
|
||||||
except TemplateDoesNotExist:
|
except TemplateDoesNotExist:
|
||||||
raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
|
raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
|
||||||
|
|
||||||
|
@ -165,7 +187,9 @@ class ConstantIncludeNode(Node):
|
||||||
try:
|
try:
|
||||||
t = get_template(template_path)
|
t = get_template(template_path)
|
||||||
self.template = t
|
self.template = t
|
||||||
except:
|
except Exception, e:
|
||||||
|
if TEMPLATE_DEBUG:
|
||||||
|
raise
|
||||||
self.template = None
|
self.template = None
|
||||||
|
|
||||||
def render(self, context):
|
def render(self, context):
|
||||||
|
@ -183,6 +207,10 @@ class IncludeNode(Node):
|
||||||
template_name = resolve_variable(self.template_name, context)
|
template_name = resolve_variable(self.template_name, context)
|
||||||
t = get_template(template_name)
|
t = get_template(template_name)
|
||||||
return t.render(context)
|
return t.render(context)
|
||||||
|
except TemplateSyntaxError, e:
|
||||||
|
if TEMPLATE_DEBUG:
|
||||||
|
raise
|
||||||
|
return ''
|
||||||
except:
|
except:
|
||||||
return '' # Fail silently for invalid included templates.
|
return '' # Fail silently for invalid included templates.
|
||||||
|
|
||||||
|
@ -236,6 +264,7 @@ def do_include(parser, token):
|
||||||
|
|
||||||
{% include "foo/some_include" %}
|
{% include "foo/some_include" %}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
bits = token.contents.split()
|
bits = token.contents.split()
|
||||||
if len(bits) != 2:
|
if len(bits) != 2:
|
||||||
raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
|
raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
|
||||||
|
|
|
@ -31,7 +31,7 @@ def load_template_source(template_name, template_dirs=None):
|
||||||
for template_dir in app_template_dirs:
|
for template_dir in app_template_dirs:
|
||||||
filepath = os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION
|
filepath = os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION
|
||||||
try:
|
try:
|
||||||
return open(filepath).read()
|
return (open(filepath).read(), filepath)
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
raise TemplateDoesNotExist, template_name
|
raise TemplateDoesNotExist, template_name
|
||||||
|
|
|
@ -18,7 +18,7 @@ def load_template_source(template_name, template_dirs=None):
|
||||||
pkg_name = 'templates/' + template_name + TEMPLATE_FILE_EXTENSION
|
pkg_name = 'templates/' + template_name + TEMPLATE_FILE_EXTENSION
|
||||||
for app in INSTALLED_APPS:
|
for app in INSTALLED_APPS:
|
||||||
try:
|
try:
|
||||||
return resource_string(app, pkg_name)
|
return (resource_string(app, pkg_name), 'egg:%s:%s ' % (app, pkg_name))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
raise TemplateDoesNotExist, template_name
|
raise TemplateDoesNotExist, template_name
|
||||||
|
|
|
@ -11,7 +11,7 @@ def load_template_source(template_name, template_dirs=None):
|
||||||
for template_dir in template_dirs:
|
for template_dir in template_dirs:
|
||||||
filepath = os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION
|
filepath = os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION
|
||||||
try:
|
try:
|
||||||
return open(filepath).read()
|
return (open(filepath).read(), filepath)
|
||||||
except IOError:
|
except IOError:
|
||||||
tried.append(filepath)
|
tried.append(filepath)
|
||||||
if template_dirs:
|
if template_dirs:
|
||||||
|
|
|
@ -1,19 +1,64 @@
|
||||||
import re
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import inspect
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from os.path import dirname, join as pathjoin
|
|
||||||
from django.core.template import Template, Context
|
from django.core.template import Template, Context
|
||||||
|
from django.utils.html import escape
|
||||||
from django.utils.httpwrappers import HttpResponseServerError, HttpResponseNotFound
|
from django.utils.httpwrappers import HttpResponseServerError, HttpResponseNotFound
|
||||||
|
import inspect, os, re, sys
|
||||||
|
from itertools import count, izip
|
||||||
|
from os.path import dirname, join as pathjoin
|
||||||
|
|
||||||
HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD')
|
HIDDEN_SETTINGS = re.compile('SECRET|PASSWORD')
|
||||||
|
|
||||||
|
def linebreak_iter(template_source):
|
||||||
|
newline_re = re.compile("^", re.M)
|
||||||
|
for match in newline_re.finditer(template_source):
|
||||||
|
yield match.start()
|
||||||
|
yield len(template_source) + 1
|
||||||
|
|
||||||
|
def get_template_exception_info(exc_type, exc_value, tb):
|
||||||
|
origin, (start, end) = exc_value.source
|
||||||
|
template_source = origin.reload()
|
||||||
|
context_lines = 10
|
||||||
|
line = 0
|
||||||
|
upto = 0
|
||||||
|
source_lines = []
|
||||||
|
linebreaks = izip(count(0), 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)
|
||||||
|
|
||||||
|
template_info = {
|
||||||
|
'message': exc_value.args[0],
|
||||||
|
'source_lines': source_lines[top:bottom],
|
||||||
|
'before': before,
|
||||||
|
'during': during,
|
||||||
|
'after': after,
|
||||||
|
'top': top ,
|
||||||
|
'bottom': bottom ,
|
||||||
|
'total': total,
|
||||||
|
'line': line,
|
||||||
|
'name': origin.name,
|
||||||
|
}
|
||||||
|
exc_info = hasattr(exc_value, 'exc_info') and exc_value.exc_info or (exc_type, exc_value, tb)
|
||||||
|
return exc_info + (template_info,)
|
||||||
|
|
||||||
def technical_500_response(request, exc_type, exc_value, tb):
|
def technical_500_response(request, exc_type, exc_value, tb):
|
||||||
"""
|
"""
|
||||||
Create a technical server error response. The last three arguments are
|
Create a technical server error response. The last three arguments are
|
||||||
the values returned from sys.exc_info() and friends.
|
the values returned from sys.exc_info() and friends.
|
||||||
"""
|
"""
|
||||||
|
template_info = None
|
||||||
|
if settings.TEMPLATE_DEBUG and hasattr(exc_value, 'source'):
|
||||||
|
exc_type, exc_value, tb, template_info = get_template_exception_info(exc_type, exc_value, tb)
|
||||||
frames = []
|
frames = []
|
||||||
while tb is not None:
|
while tb is not None:
|
||||||
filename = tb.tb_frame.f_code.co_filename
|
filename = tb.tb_frame.f_code.co_filename
|
||||||
|
@ -21,16 +66,16 @@ def technical_500_response(request, exc_type, exc_value, tb):
|
||||||
lineno = tb.tb_lineno - 1
|
lineno = tb.tb_lineno - 1
|
||||||
pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7)
|
pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7)
|
||||||
frames.append({
|
frames.append({
|
||||||
'tb' : tb,
|
'tb': tb,
|
||||||
'filename' : filename,
|
'filename': filename,
|
||||||
'function' : function,
|
'function': function,
|
||||||
'lineno' : lineno,
|
'lineno': lineno,
|
||||||
'vars' : tb.tb_frame.f_locals.items(),
|
'vars': tb.tb_frame.f_locals.items(),
|
||||||
'id' : id(tb),
|
'id': id(tb),
|
||||||
'pre_context' : pre_context,
|
'pre_context': pre_context,
|
||||||
'context_line' : context_line,
|
'context_line': context_line,
|
||||||
'post_context' : post_context,
|
'post_context': post_context,
|
||||||
'pre_context_lineno' : pre_context_lineno,
|
'pre_context_lineno': pre_context_lineno,
|
||||||
})
|
})
|
||||||
tb = tb.tb_next
|
tb = tb.tb_next
|
||||||
|
|
||||||
|
@ -46,14 +91,14 @@ def technical_500_response(request, exc_type, exc_value, tb):
|
||||||
|
|
||||||
t = Template(TECHNICAL_500_TEMPLATE)
|
t = Template(TECHNICAL_500_TEMPLATE)
|
||||||
c = Context({
|
c = Context({
|
||||||
'exception_type' : exc_type.__name__,
|
'exception_type': exc_type.__name__,
|
||||||
'exception_value' : exc_value,
|
'exception_value': exc_value,
|
||||||
'frames' : frames,
|
'frames': frames,
|
||||||
'lastframe' : frames[-1],
|
'lastframe': frames[-1],
|
||||||
'request' : request,
|
'request': request,
|
||||||
'request_protocol' : os.environ.get("HTTPS") == "on" and "https" or "http",
|
'request_protocol': os.environ.get("HTTPS") == "on" and "https" or "http",
|
||||||
'settings' : settings_dict,
|
'settings': settings_dict,
|
||||||
|
'template_info': template_info,
|
||||||
})
|
})
|
||||||
return HttpResponseServerError(t.render(c), mimetype='text/html')
|
return HttpResponseServerError(t.render(c), mimetype='text/html')
|
||||||
|
|
||||||
|
@ -69,12 +114,12 @@ def technical_404_response(request, exception):
|
||||||
|
|
||||||
t = Template(TECHNICAL_404_TEMPLATE)
|
t = Template(TECHNICAL_404_TEMPLATE)
|
||||||
c = Context({
|
c = Context({
|
||||||
'root_urlconf' : settings.ROOT_URLCONF,
|
'root_urlconf': settings.ROOT_URLCONF,
|
||||||
'urlpatterns' : tried,
|
'urlpatterns': tried,
|
||||||
'reason' : str(exception),
|
'reason': str(exception),
|
||||||
'request' : request,
|
'request': request,
|
||||||
'request_protocol' : os.environ.get("HTTPS") == "on" and "https" or "http",
|
'request_protocol': os.environ.get("HTTPS") == "on" and "https" or "http",
|
||||||
'settings' : dict([(k, getattr(settings, k)) for k in dir(settings) if k.isupper()]),
|
'settings': dict([(k, getattr(settings, k)) for k in dir(settings) if k.isupper()]),
|
||||||
})
|
})
|
||||||
return HttpResponseNotFound(t.render(c), mimetype='text/html')
|
return HttpResponseNotFound(t.render(c), mimetype='text/html')
|
||||||
|
|
||||||
|
@ -144,6 +189,9 @@ TECHNICAL_500_TEMPLATE = """
|
||||||
#summary table { border:none; background:transparent; }
|
#summary table { border:none; background:transparent; }
|
||||||
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
|
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
|
||||||
#requestinfo h3 { margin-bottom:-1em; }
|
#requestinfo h3 { margin-bottom:-1em; }
|
||||||
|
table.source td { font-family: monospace; white-space: pre; }
|
||||||
|
span.specific { background:#ffcab7; }
|
||||||
|
.error { background: #ffc; }
|
||||||
</style>
|
</style>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
//<!--
|
//<!--
|
||||||
|
@ -221,7 +269,24 @@ TECHNICAL_500_TEMPLATE = """
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
{% if template_info %}
|
||||||
|
<div id="template">
|
||||||
|
<h2>Template</h2>
|
||||||
|
In template {{ template_info.name }}, error at line {{ template_info.line }}
|
||||||
|
<div>{{ template_info.message|escape }}</div>
|
||||||
|
<table class="source{% if template_info.top %} cut-top{% endif %}{% ifnotequal template_info.bottom template_info.total %} cut-bottom{% endifnotequal %}">
|
||||||
|
{% for source_line in template_info.source_lines %}
|
||||||
|
{% ifequal source_line.0 template_info.line %}
|
||||||
|
<tr class="error"><td>{{ source_line.0 }}</td>
|
||||||
|
<td>{{ template_info.before }}<span class="specific">{{ template_info.during }}</span>{{ template_info.after }}</td></tr>
|
||||||
|
{% else %}
|
||||||
|
<tr><td>{{ source_line.0 }}</td>
|
||||||
|
<td> {{ source_line.1 }}</td></tr>
|
||||||
|
{% endifequal %}
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div id="traceback">
|
<div id="traceback">
|
||||||
<h2>Traceback <span>(innermost last)</span></h2>
|
<h2>Traceback <span>(innermost last)</span></h2>
|
||||||
<ul class="traceback">
|
<ul class="traceback">
|
||||||
|
|
|
@ -99,6 +99,9 @@ TEMPLATE_TESTS = {
|
||||||
# Chained filters, with an argument to the first one
|
# Chained filters, with an argument to the first one
|
||||||
'basic-syntax29': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
|
'basic-syntax29': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
|
||||||
|
|
||||||
|
#Escaped string as argument
|
||||||
|
'basic-syntax30': (r"""{{ var|default_if_none:" endquote\" hah" }}""", {"var": None}, ' endquote" hah'),
|
||||||
|
|
||||||
### IF TAG ################################################################
|
### IF TAG ################################################################
|
||||||
'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
|
'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
|
||||||
'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
|
'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
|
||||||
|
@ -225,6 +228,23 @@ TEMPLATE_TESTS = {
|
||||||
# Raise exception for custom tags used in child with {% load %} tag in parent, not in child
|
# Raise exception for custom tags used in child with {% load %} tag in parent, not in child
|
||||||
'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
|
'exception04': ("{% extends 'inheritance17' %}{% block first %}{% echo 400 %}5678{% endblock %}", {}, template.TemplateSyntaxError),
|
||||||
|
|
||||||
|
'multiline01': ("""
|
||||||
|
Hello,
|
||||||
|
boys.
|
||||||
|
How
|
||||||
|
are
|
||||||
|
you
|
||||||
|
gentlemen.
|
||||||
|
""",
|
||||||
|
{},
|
||||||
|
"""
|
||||||
|
Hello,
|
||||||
|
boys.
|
||||||
|
How
|
||||||
|
are
|
||||||
|
you
|
||||||
|
gentlemen.
|
||||||
|
""" ),
|
||||||
# simple translation of a string delimited by '
|
# simple translation of a string delimited by '
|
||||||
'i18n01': ("{% load i18n %}{% trans 'xxxyyyxxx' %}", {}, "xxxyyyxxx"),
|
'i18n01': ("{% load i18n %}{% trans 'xxxyyyxxx' %}", {}, "xxxyyyxxx"),
|
||||||
|
|
||||||
|
@ -268,7 +288,7 @@ TEMPLATE_TESTS = {
|
||||||
def test_template_loader(template_name, template_dirs=None):
|
def test_template_loader(template_name, template_dirs=None):
|
||||||
"A custom template loader that loads the unit-test templates."
|
"A custom template loader that loads the unit-test templates."
|
||||||
try:
|
try:
|
||||||
return TEMPLATE_TESTS[template_name][0]
|
return ( TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name )
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise template.TemplateDoesNotExist, template_name
|
raise template.TemplateDoesNotExist, template_name
|
||||||
|
|
||||||
|
@ -308,7 +328,7 @@ def run_tests(verbosity=0, standalone=False):
|
||||||
print "Template test: %s -- FAILED. Expected %r, got %r" % (name, vals[2], output)
|
print "Template test: %s -- FAILED. Expected %r, got %r" % (name, vals[2], output)
|
||||||
failed_tests.append(name)
|
failed_tests.append(name)
|
||||||
loader.template_source_loaders = old_template_loaders
|
loader.template_source_loaders = old_template_loaders
|
||||||
|
deactivate()
|
||||||
if failed_tests and not standalone:
|
if failed_tests and not standalone:
|
||||||
msg = "Template tests %s failed." % failed_tests
|
msg = "Template tests %s failed." % failed_tests
|
||||||
if not verbosity:
|
if not verbosity:
|
||||||
|
|
Loading…
Reference in New Issue