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('' % capfirst(header)) + yield {"text": header} else: if isinstance(f.rel, meta.ManyToOne) and f.null: raw_template.append('' % 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 = '%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 = '%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 ""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('
%s%s
\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 = '%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 = '%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 = ' ' + 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