Kaydet (Commit) 5d863f1f authored tarafından Adrian Holovaty's avatar Adrian Holovaty

Fixed #603 -- Added template debugging errors to pretty error-page output, if…

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
üst cfc5786d
......@@ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _
####################
DEBUG = False
TEMPLATE_DEBUG = False
# Whether to use the "Etag" header. This saves bandwidth but slows down performance.
USE_ETAGS = False
......
# Django settings for {{ project_name }} project.
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
# ('Your Name', 'your_email@domain.com'),
......
......@@ -192,6 +192,7 @@ class RegroupNode(Node):
for obj in obj_list:
grouper = resolve_variable_with_filters('var.%s' % self.expression, \
Context({'var': obj}))
# TODO: Is this a sensible way to determine equality?
if output and repr(output[-1]['grouper']) == repr(grouper):
output[-1]['list'].append(obj)
else:
......@@ -628,8 +629,8 @@ def do_load(parser, token):
# check at compile time that the module can be imported
try:
LoadNode.load_taglib(taglib)
except ImportError:
raise TemplateSyntaxError, "'%s' is not a valid tag library" % taglib
except ImportError, e:
raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e)
return LoadNode(taglib)
def do_now(parser, token):
......
......@@ -8,6 +8,10 @@
# name is the template name.
# 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
# specifies whether the loader can be used in this Python installation. Each
# loader is responsible for setting this when it's initialized.
......@@ -17,8 +21,8 @@
# installed, because pkg_resources is necessary to read eggs.
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.conf.settings import TEMPLATE_LOADERS
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, TEMPLATE_DEBUG
template_source_loaders = []
for path in TEMPLATE_LOADERS:
......@@ -38,14 +42,32 @@ for path in TEMPLATE_LOADERS:
else:
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:
try:
return loader(name, dirs)
source, display_name = loader(name, dirs)
return (source, make_origin(display_name, loader, name, dirs))
except TemplateDoesNotExist:
pass
raise TemplateDoesNotExist, name
def load_template_source(name, dirs=None):
find_template_source(name, dirs)[0]
class ExtendsError(Exception):
pass
......@@ -54,14 +76,14 @@ def get_template(template_name):
Returns a compiled Template object for the given template name,
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,
handling template inheritance recursively.
"""
return Template(source)
return Template(source, origin)
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
raise TemplateSyntaxError, error_msg
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:
raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
......@@ -165,7 +187,9 @@ class ConstantIncludeNode(Node):
try:
t = get_template(template_path)
self.template = t
except:
except Exception, e:
if TEMPLATE_DEBUG:
raise
self.template = None
def render(self, context):
......@@ -183,6 +207,10 @@ class IncludeNode(Node):
template_name = resolve_variable(self.template_name, context)
t = get_template(template_name)
return t.render(context)
except TemplateSyntaxError, e:
if TEMPLATE_DEBUG:
raise
return ''
except:
return '' # Fail silently for invalid included templates.
......@@ -236,6 +264,7 @@ def do_include(parser, token):
{% include "foo/some_include" %}
"""
bits = token.contents.split()
if len(bits) != 2:
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:
filepath = os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION
try:
return open(filepath).read()
return (open(filepath).read(), filepath)
except IOError:
pass
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
for app in INSTALLED_APPS:
try:
return resource_string(app, pkg_name)
return (resource_string(app, pkg_name), 'egg:%s:%s ' % (app, pkg_name))
except:
pass
raise TemplateDoesNotExist, template_name
......
......@@ -11,7 +11,7 @@ def load_template_source(template_name, template_dirs=None):
for template_dir in template_dirs:
filepath = os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION
try:
return open(filepath).read()
return (open(filepath).read(), filepath)
except IOError:
tried.append(filepath)
if template_dirs:
......
import re
import os
import sys
import inspect
from django.conf import settings
from os.path import dirname, join as pathjoin
from django.core.template import Template, Context
from django.utils.html import escape
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')
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):
"""
Create a technical server error response. The last three arguments are
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 = []
while tb is not None:
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
pre_context_lineno, pre_context, context_line, post_context = _get_lines_from_file(filename, lineno, 7)
frames.append({
'tb' : tb,
'filename' : filename,
'function' : function,
'lineno' : lineno,
'vars' : tb.tb_frame.f_locals.items(),
'id' : id(tb),
'pre_context' : pre_context,
'context_line' : context_line,
'post_context' : post_context,
'pre_context_lineno' : pre_context_lineno,
'tb': tb,
'filename': filename,
'function': function,
'lineno': lineno,
'vars': tb.tb_frame.f_locals.items(),
'id': id(tb),
'pre_context': pre_context,
'context_line': context_line,
'post_context': post_context,
'pre_context_lineno': pre_context_lineno,
})
tb = tb.tb_next
......@@ -46,14 +91,14 @@ def technical_500_response(request, exc_type, exc_value, tb):
t = Template(TECHNICAL_500_TEMPLATE)
c = Context({
'exception_type' : exc_type.__name__,
'exception_value' : exc_value,
'frames' : frames,
'lastframe' : frames[-1],
'request' : request,
'request_protocol' : os.environ.get("HTTPS") == "on" and "https" or "http",
'settings' : settings_dict,
'exception_type': exc_type.__name__,
'exception_value': exc_value,
'frames': frames,
'lastframe': frames[-1],
'request': request,
'request_protocol': os.environ.get("HTTPS") == "on" and "https" or "http",
'settings': settings_dict,
'template_info': template_info,
})
return HttpResponseServerError(t.render(c), mimetype='text/html')
......@@ -69,12 +114,12 @@ def technical_404_response(request, exception):
t = Template(TECHNICAL_404_TEMPLATE)
c = Context({
'root_urlconf' : settings.ROOT_URLCONF,
'urlpatterns' : tried,
'reason' : str(exception),
'request' : request,
'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()]),
'root_urlconf': settings.ROOT_URLCONF,
'urlpatterns': tried,
'reason': str(exception),
'request': request,
'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()]),
})
return HttpResponseNotFound(t.render(c), mimetype='text/html')
......@@ -144,6 +189,9 @@ TECHNICAL_500_TEMPLATE = """
#summary table { border:none; background:transparent; }
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
#requestinfo h3 { margin-bottom:-1em; }
table.source td { font-family: monospace; white-space: pre; }
span.specific { background:#ffcab7; }
.error { background: #ffc; }
</style>
<script type="text/javascript">
//<!--
......@@ -221,7 +269,24 @@ TECHNICAL_500_TEMPLATE = """
</tr>
</table>
</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">
<h2>Traceback <span>(innermost last)</span></h2>
<ul class="traceback">
......
......@@ -99,6 +99,9 @@ TEMPLATE_TESTS = {
# Chained filters, with an argument to the first one
'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-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
'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
'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 '
'i18n01': ("{% load i18n %}{% trans 'xxxyyyxxx' %}", {}, "xxxyyyxxx"),
......@@ -268,7 +288,7 @@ TEMPLATE_TESTS = {
def test_template_loader(template_name, template_dirs=None):
"A custom template loader that loads the unit-test templates."
try:
return TEMPLATE_TESTS[template_name][0]
return ( TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name )
except KeyError:
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)
failed_tests.append(name)
loader.template_source_loaders = old_template_loaders
deactivate()
if failed_tests and not standalone:
msg = "Template tests %s failed." % failed_tests
if not verbosity:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment