diff --git a/django/contrib/admin/templates/admin/template_debug.html b/django/contrib/admin/templates/admin/template_debug.html
index 964bf747a3..55569e539a 100644
--- a/django/contrib/admin/templates/admin/template_debug.html
+++ b/django/contrib/admin/templates/admin/template_debug.html
@@ -3,9 +3,10 @@
{% block content %}
Error in Template
-
- {{message}}
-
+
+{{message}}
+{{traceback}}
+
{%if top%}
...
diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py
index c19f79c25a..48cf2d12a6 100644
--- a/django/contrib/admin/templatetags/admin_list.py
+++ b/django/contrib/admin/templatetags/admin_list.py
@@ -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('
\n\n\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('%s | ' % capfirst(header))
+ yield {"text": header}
else:
if isinstance(f.rel, meta.ManyToOne) and f.null:
raw_template.append('%s | ' % 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('%s | ' % \
- ((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('
\n\n')
- # Result rows.
- pk = lookup_opts.pk.name
- for i, result in enumerate(result_list):
- raw_template.append('\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 = '
' % (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 = '
' % (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 " | "s.
- if result_repr == '':
- result_repr = ' '
- if j == 0: # First column is a special case
- result_id = getattr(result, pk)
- raw_template.append('%s | ' % \
- (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('%s | ' % (row_class, result_repr))
- raw_template.append('
\n')
- del result_list # to free memory
- raw_template.append('
\n')
- else:
- raw_template.append('
No %s matched your search criteria.
' % 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 = '

' % (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 = '

' % (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 = ' '
+ if first: # First column is a special case
+ first = False
+ result_id = getattr(result, pk)
+ yield ('
%s | ' % \
+ (row_class, result_id, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %r); return false;"' % result_id or ''), result_repr))
+ else:
+ yield ('
%s | ' % (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):
diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py
index 009c57eaad..912f903276 100644
--- a/django/contrib/admin/views/main.py
+++ b/django/contrib/admin/views/main.py
@@ -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:
diff --git a/django/core/meta/__init__.py b/django/core/meta/__init__.py
index 80c422472a..3d6a7ef1e3 100644
--- a/django/core/meta/__init__.py
+++ b/django/core/meta/__init__.py
@@ -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.
diff --git a/django/core/template/__init__.py b/django/core/template/__init__.py
index 7437a71e3f..82b79986d2 100644
--- a/django/core/template/__init__.py
+++ b/django/core/template/__init__.py
@@ -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):
diff --git a/django/core/template/loader.py b/django/core/template/loader.py
index b3ccad89da..cb687a7edb 100644
--- a/django/core/template/loader.py
+++ b/django/core/template/loader.py
@@ -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
diff --git a/django/middleware/template_debug.py b/django/middleware/template_debug.py
index 222dd5f47d..951cd31959 100644
--- a/django/middleware/template_debug.py
+++ b/django/middleware/template_debug.py
@@ -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
- })
-
\ No newline at end of file
+ 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
+ }),
+ )
+ )
\ No newline at end of file