1
0
mirror of https://github.com/django/django.git synced 2025-07-04 17:59:13 +00:00

More repr cleanups.

Fixed template debugging and now it captures exceptions that occur during rendering. This pinpoints which template tag caused the exceptions and cuts down the stacktrace to what happened inside the template tag. 

Refactored some change list stuff. 


git-svn-id: http://code.djangoproject.com/svn/django/branches/new-admin@1046 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Robert Wittams 2005-11-02 15:14:02 +00:00
parent 0f9d7018a0
commit 13c27a87d6
7 changed files with 162 additions and 135 deletions

View File

@ -3,9 +3,10 @@
{% block content %}
<div>
<h2> Error in Template </h2>
<pre>
{{message}}
</pre>
<pre>
{{message}}
{{traceback}}
</pre>
{%if top%}
...

View File

@ -106,16 +106,11 @@ def pagination(cl):
}
pagination = inclusion_tag('admin/pagination')(pagination)
#@simple_tag
def result_list(cl):
result_list, lookup_opts, mod, order_field, order_type, params, is_popup, opts = \
cl.result_list, cl.lookup_opts, cl.mod, cl.order_field, cl.order_type, cl.params, cl.is_popup, cl.opts
def result_headers(cl):
lookup_opts = cl.lookup_opts
raw_template = []
if result_list:
# Table headers.
raw_template.append('<table cellspacing="0">\n<thead>\n<tr>\n')
for i, field_name in enumerate(lookup_opts.admin.list_display):
for i, field_name in enumerate(lookup_opts.admin.list_display):
try:
f = lookup_opts.get_field(field_name)
except meta.FieldDoesNotExist:
@ -131,91 +126,100 @@ def result_list(cl):
except AttributeError:
header = func.__name__
# Non-field list_display values don't get ordering capability.
raw_template.append('<th>%s</th>' % capfirst(header))
yield {"text": header}
else:
if isinstance(f.rel, meta.ManyToOne) and f.null:
raw_template.append('<th>%s</th>' % capfirst(f.verbose_name))
yield {"text": f.verbose_name}
else:
th_classes = []
new_order_type = 'asc'
if field_name == order_field:
if field_name == cl.order_field:
th_classes.append('sorted %sending' % order_type.lower())
new_order_type = {'asc': 'desc', 'desc': 'asc'}[order_type.lower()]
raw_template.append('<th%s><a href="%s">%s</a></th>' % \
((th_classes and ' class="%s"' % ' '.join(th_classes) or ''),
cl.get_query_string( {ORDER_VAR: i, ORDER_TYPE_VAR: new_order_type}),
capfirst(f.verbose_name)))
raw_template.append('</tr>\n</thead>\n')
# Result rows.
pk = lookup_opts.pk.name
for i, result in enumerate(result_list):
raw_template.append('<tr class="row%s">\n' % (i % 2 + 1))
for j, field_name in enumerate(lookup_opts.admin.list_display):
row_class = ''
try:
f = lookup_opts.get_field(field_name)
except meta.FieldDoesNotExist:
# For non-field list_display values, the value is a method
# name. Execute the method.
try:
result_repr = strip_tags(str(getattr(result, field_name)()))
except ObjectDoesNotExist:
result_repr = EMPTY_CHANGELIST_VALUE
yield {"text" : f.verbose_name,
"sortable": True,
"order" : new_order_type,
"class_attrib" : (th_classes and ' class="%s"' % ' '.join(th_classes) or '') }
def items_for_result(cl, result):
first = True
pk = cl.lookup_opts.pk.name
for field_name in cl.lookup_opts.admin.list_display:
row_class = ''
try:
f = cl.lookup_opts.get_field(field_name)
except meta.FieldDoesNotExist:
# For non-field list_display values, the value is a method
# name. Execute the method.
try:
result_repr = strip_tags(str(getattr(result, field_name)()))
except ObjectDoesNotExist:
result_repr = EMPTY_CHANGELIST_VALUE
else:
field_val = getattr(result, f.column)
if isinstance(f.rel, meta.ManyToOne):
if field_val is not None:
result_repr = getattr(result, 'get_%s' % f.name)()
else:
field_val = getattr(result, f.column)
# Foreign-key fields are special: Use the repr of the
# related object.
if isinstance(f.rel, meta.ManyToOne):
if field_val is not None:
result_repr = getattr(result, 'get_%s' % f.name)()
else:
result_repr = EMPTY_CHANGELIST_VALUE
# Dates are special: They're formatted in a certain way.
elif isinstance(f, meta.DateField):
if field_val:
if isinstance(f, meta.DateTimeField):
result_repr = dateformat.format(field_val, 'N j, Y, P')
else:
result_repr = dateformat.format(field_val, 'N j, Y')
else:
result_repr = EMPTY_CHANGELIST_VALUE
row_class = ' class="nowrap"'
# Booleans are special: We use images.
elif isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField):
BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
result_repr = '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
# ImageFields are special: Use a thumbnail.
elif isinstance(f, meta.ImageField):
from django.parts.media.photos import get_thumbnail_url
result_repr = '<img src="%s" alt="%s" title="%s" />' % (get_thumbnail_url(getattr(result, 'get_%s_url' % f.name)(), '120'), field_val, field_val)
# FloatFields are special: Zero-pad the decimals.
elif isinstance(f, meta.FloatField):
if field_val is not None:
result_repr = ('%%.%sf' % f.decimal_places) % field_val
else:
result_repr = EMPTY_CHANGELIST_VALUE
# Fields with choices are special: Use the representation
# of the choice.
elif f.choices:
result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE)
result_repr = EMPTY_CHANGELIST_VALUE
# Dates are special: They're formatted in a certain way.
elif isinstance(f, meta.DateField):
if field_val:
if isinstance(f, meta.DateTimeField):
result_repr = dateformat.format(field_val, 'N j, Y, P')
else:
result_repr = strip_tags(str(field_val))
# Some browsers don't like empty "<td></td>"s.
if result_repr == '':
result_repr = '&nbsp;'
if j == 0: # First column is a special case
result_id = getattr(result, pk)
raw_template.append('<th%s><a href="%s/"%s>%s</a></th>' % \
(row_class, result_id, (is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %r); return false;"' % result_id or ''), result_repr))
result_repr = dateformat.format(field_val, 'N j, Y')
else:
raw_template.append('<td%s>%s</td>' % (row_class, result_repr))
raw_template.append('</tr>\n')
del result_list # to free memory
raw_template.append('</table>\n')
else:
raw_template.append('<p>No %s matched your search criteria.</p>' % opts.verbose_name_plural)
return ''.join(raw_template)
result_list = simple_tag(result_list)
result_repr = EMPTY_CHANGELIST_VALUE
row_class = ' class="nowrap"'
# Booleans are special: We use images.
elif isinstance(f, meta.BooleanField) or isinstance(f, meta.NullBooleanField):
BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
result_repr = '<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val)
# ImageFields are special: Use a thumbnail.
elif isinstance(f, meta.ImageField):
from django.parts.media.photos import get_thumbnail_url
result_repr = '<img src="%s" alt="%s" title="%s" />' % (get_thumbnail_url(getattr(result, 'get_%s_url' % f.name)(), '120'), field_val, field_val)
# FloatFields are special: Zero-pad the decimals.
elif isinstance(f, meta.FloatField):
if field_val is not None:
result_repr = ('%%.%sf' % f.decimal_places) % field_val
else:
result_repr = EMPTY_CHANGELIST_VALUE
# Fields with choices are special: Use the representation
# of the choice.
elif f.choices:
result_repr = dict(f.choices).get(field_val, EMPTY_CHANGELIST_VALUE)
else:
result_repr = strip_tags(str(field_val))
if result_repr == '':
result_repr = '&nbsp;'
if first: # First column is a special case
first = False
result_id = getattr(result, pk)
yield ('<th%s><a href="%s/"%s>%s</a></th>' % \
(row_class, result_id, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %r); return false;"' % result_id or ''), result_repr))
else:
yield ('<td%s>%s</td>' % (row_class, result_repr))
def results(cl):
for res in cl.result_list:
yield list(items_for_result(cl,res))
#@inclusion_tag("admin/change_list_results")
def result_list(cl):
res = list(results(cl))
return {'cl': cl,
'result_headers': list(result_headers(cl)),
'results': list(results(cl)), }
result_list = inclusion_tag("admin/change_list_results")(result_list)
#@simple_tag
def date_hierarchy(cl):

View File

@ -345,10 +345,15 @@ class ChangeList(object):
# If the order-by field is a field with a relationship, order by the value
# in the related table.
lookup_order_field = order_field
if isinstance(lookup_opts.get_field(order_field).rel, meta.ManyToOne):
try:
f = lookup_opts.get_field(order_field)
rel_ordering = f.rel.to.ordering and f.rel.to.ordering[0] or f.rel.to.pk.column
lookup_order_field = '%s.%s' % (f.rel.to.db_table, rel_ordering)
except meta.FieldDoesNotExist:
pass
else:
if isinstance(lookup_opts.get_field(order_field).rel, meta.ManyToOne):
f = lookup_opts.get_field(order_field)
rel_ordering = f.rel.to.ordering and f.rel.to.ordering[0] or f.rel.to.pk.column
lookup_order_field = '%s.%s' % (f.rel.to.db_table, rel_ordering)
# Use select_related if one of the list_display options is a field with a
# relationship.
for field_name in lookup_opts.admin.list_display:

View File

@ -1728,11 +1728,11 @@ def manipulator_save(opts, klass, add, change, self, new_data):
# Calculate whether any fields have changed.
if change:
if not old_rel_obj: # This object didn't exist before.
self.fields_added.append('%s "%r"' % (related.opts.verbose_name, new_rel_obj))
self.fields_added.append('%s "%s"' % (related.opts.verbose_name, new_rel_obj))
else:
for f in related.opts.fields:
if not f.primary_key and f != related.field and str(getattr(old_rel_obj, f.column)) != str(getattr(new_rel_obj, f.column)):
self.fields_changed.append('%s for %s "%r"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, related.opts.verbose_name, new_rel_obj))
# Save many-to-many objects.
for f in related.opts.many_to_many:
@ -1745,7 +1745,7 @@ def manipulator_save(opts, klass, add, change, self, new_data):
# the primary key (ID) was provided, delete the item.
if change and all_cores_blank and old_rel_obj:
new_rel_obj.delete()
self.fields_deleted.append('%s "%r"' % (related.opts.verbose_name, old_rel_obj))
self.fields_deleted.append('%s "%s"' % (related.opts.verbose_name, old_rel_obj))
# Save the order, if applicable.

View File

@ -143,14 +143,7 @@ class Template:
return self.nodelist.render(context)
def compile_string(template_string, origin):
"Compiles template_string into NodeList ready for rendering"
if TEMPLATE_DEBUG:
lexer_factory = DebugLexer
parser_factory = DebugParser
else:
lexer_factory = Lexer
parser_factory = Parser
"Compiles template_string into NodeList ready for rendering"
lexer = lexer_factory(template_string, origin)
parser = parser_factory(lexer.tokenize())
return parser.parse()
@ -356,7 +349,9 @@ class Parser(object):
def delete_first_token(self):
del self.tokens[0]
def format_source(source):
return "at %s, line %d" % source
class DebugParser(Parser):
def __init__(self, lexer):
super(DebugParser, self).__init__(lexer)
@ -384,13 +379,13 @@ class DebugParser(Parser):
super(DebugParser, self).extend_nodelist(nodelist, node, token)
def empty_variable(self, token):
raise self.error( token.source, "Empty variable tag %s" % self.format_source(token.source))
raise self.error( token.source, "Empty variable tag %s" % format_source(token.source))
def empty_block_tag(self, token):
raise self.error( token.source, "Empty block tag %s" % self.format_source(token.source))
raise self.error( token.source, "Empty block tag %s" % format_source(token.source))
def invalid_block_tag(self, token, command):
raise self.error( token.source, "Invalid block tag: '%s' %s" % (command, self.format_source(token.source)))
raise self.error( token.source, "Invalid block tag: '%s' %s" % (command, format_source(token.source)))
def unclosed_block_tag(self, token, parse_until):
(command, (origin,line)) = self.command_stack.pop()
@ -402,6 +397,13 @@ class DebugParser(Parser):
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 FilterParser:
"""Parse a variable token and its optional filters (all as a single string),
and return a list of tuples of the filter name and arguments.
@ -589,7 +591,6 @@ class RegexFilterParser(object):
raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:]
self.var , self.filters = var, filters
def get_filters_from_token(token):
"Convenient wrapper for FilterParser"
p = RegexFilterParser(token)
@ -682,12 +683,7 @@ class NodeList(list):
bits = []
for node in self:
if isinstance(node, Node):
try:
result = node.render(context)
except TemplateSyntaxError, e:
if not self.handle_render_error(node, e):
raise
bits.append(result)
bits.append(self.render_node(node, context))
else:
bits.append(node)
return ''.join(bits)
@ -699,14 +695,30 @@ class NodeList(list):
nodes.extend(node.get_nodes_by_type(nodetype))
return nodes
def handle_render_error(self, node, exception):
pass
def render_node(self, node, context):
return(node.render(context))
class DebugNodeList(NodeList):
def handle_render_error(self, node, exception):
if not hasattr(exception, 'source'):
exception.source = node.source
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, e:
from traceback import extract_tb, format_list, format_exception_only
from sys import exc_info
t,v,tb = exc_info()
frames = extract_tb(tb)
frames.pop(0)
wrapped = TemplateSyntaxError( ' While rendering %s , caught exception:\n %s'
% ( format_source(node.source),
"".join(format_exception_only(t,v)).replace('\n','')))
wrapped.source = node.source
wrapped.traceback = "".join(format_list(frames))
raise wrapped
return result
class TextNode(Node):
def __init__(self, s):

View File

@ -47,11 +47,11 @@ class LoaderOrigin(Origin):
self.loader, self.name, self.dirs = loader, name, dirs
def reload(self):
return self.loader(self.name, self.dirs)
return self.loader(self.name, self.dirs)[0]
def make_origin(display_name, loader, name, dirs):
if TEMPLATE_DEBUG:
LoaderOrigin(display_name, loader, name, dirs)
return LoaderOrigin(display_name, loader, name, dirs)
else:
return None

View File

@ -1,25 +1,30 @@
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):
from django.core.template.loader import render_to_string
from django.utils.html import escape
from django.utils.httpwrappers import HttpResponseServerError
from django.core.extensions import DjangoContext
from itertools import count, izip
context_lines = 10
if hasattr(exception, 'source'):
origin, line = exception.source
template_source = origin.reload()
source_lines = [ (i + 1,s) for (i,s) in enumerate(escape(template_source).split("\n"))]
source_lines = [ (i,s) for (i,s) in izip(count(1), 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
})
traceback = hasattr(exception, 'traceback') and exception.traceback or ''
return HttpResponseServerError(
render_to_string('template_debug',
DjangoContext(request, {
'message' : exception.args[0],
'traceback' : traceback,
'source_lines' : source_lines[top:bottom],
'top': top ,
'bottom': bottom ,
'total' : total,
'line' : line
}),
)
)