1
0
mirror of https://github.com/django/django.git synced 2025-07-06 02:39:12 +00:00

Template fixes for inline tabular to let errors show up properly

Typo fix for extends tag doc

Hugely improved messages for template syntax errors. 
Tokens and nodes are tagged with file and line numbers.
These are used in error messages. 

Example: 

TemplateSyntaxError: Unclosed tag 'if' starting at /usr/lib/python2.4/site-packages/django/conf/admin_templates/admin_submit_line.html, line 6. Looking for one of: else, endif 




git-svn-id: http://code.djangoproject.com/svn/django/branches/new-admin@796 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Robert Wittams 2005-10-07 01:13:32 +00:00
parent f9e1deb8b3
commit c7d7e4cb78
7 changed files with 95 additions and 39 deletions

View File

@ -21,7 +21,7 @@
<tr class="{% cycle row1,row2 %}"> <tr class="{% cycle row1,row2 %}">
{% for bound_field in fcw.bound_fields %} {% for bound_field in fcw.bound_fields %}
{% if not bound_field.not_in_table %} {% if not bound_field.not_in_table %}
<td "{{ bound_field.cell_class_attribute}}"> <td {{bound_field.cell_class_attribute}}>
{% field_widget bound_field %} {% field_widget bound_field %}
</td> </td>
{% endif %} {% endif %}

View File

@ -1,7 +1,7 @@
<div class="submit-row"> <div class="submit-row">
{% if show_delete_link %}<p class="float-left"><a href="delete/" class="deletelink">Delete</a></p>{% endif %} {% if show_delete_link %}<p class="float-left"><a href="delete/" class="deletelink">Delete</a></p>{% endif %}
{% if show_save_as_new %}<input type="submit" value="Save as new" name="_saveasnew" {{onclick_attrib}}/>{%endif%} {% if show_save_as_new %}<input type="submit" value="Save as new" name="_saveasnew" {{onclick_attrib}}/>{%endif%}
{% if show_save_and_add_another %}<input type="submit" value="Save and add another" name="_addanother" {{onclick_attrib}} />{% endif %} {% if show_save_and_add_another %}<input type="submit" value="Save and add another" name="_addanother" {{onclick_attrib}} />{% endif %}
{% if show_save_and_continue %}<input type="submit" value="Save and continue editing" name="_continue" {{onclick_attrib}}/>{% endif %} {% if show_save_and_continue %}<input type="submit" value="Save and continue editing" name="_continue" {{onclick_attrib}}/>{% endif %}
{% if show_save %}<input type="submit" value="Save" class="default" {{onclick_attrib}}/>{% endif %} {% if show_save %}<input type="submit" value="Save" class="default" {{onclick_attrib}}/>
</div> </div>

View File

@ -230,11 +230,17 @@ class SsiNode(template.Node):
class ConstantIncludeNode(template.Node): class ConstantIncludeNode(template.Node):
def __init__(self, template_path): def __init__(self, template_path):
t = template_loader.get_template(template_path) try:
self.nodelist = t.nodelist t = template_loader.get_template(template_path)
self.nodelist = t.nodelist
except Exception, e:
self.nodelist = None
def render(self, context): def render(self, context):
return self.nodelist.render(context) if self.nodelist:
return self.nodelist.render(context)
else:
return ''
class IncludeNode(template.Node): class IncludeNode(template.Node):
def __init__(self, template_path_var): def __init__(self, template_path_var):
@ -247,7 +253,6 @@ class IncludeNode(template.Node):
t = template_loader.get_template(template_path) t = template_loader.get_template(template_path)
return t.render(context) return t.render(context)
except Exception, e: except Exception, e:
print e
return '' # Fail silently for invalid included templates. return '' # Fail silently for invalid included templates.

View File

@ -74,10 +74,18 @@ VARIABLE_TAG_END = '}}'
ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.' ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.'
#What to report as the origin of templates that come from non file sources (eg strings)
UNKNOWN_SOURCE="<unknown source>"
#match starts of lines
newline_re = re.compile("^", re.M);
# 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)))
# global dict used by register_tag; maps custom tags to callback functions # global dict used by register_tag; maps custom tags to callback functions
registered_tags = {} registered_tags = {}
@ -102,9 +110,9 @@ class SilentVariableFailure(Exception):
pass pass
class Template: class Template:
def __init__(self, template_string): def __init__(self, template_string, filename=UNKNOWN_SOURCE):
"Compilation stage" "Compilation stage"
self.nodelist = compile_string(template_string) self.nodelist = compile_string(template_string, filename)
def __iter__(self): def __iter__(self):
for node in self.nodelist: for node in self.nodelist:
@ -115,9 +123,9 @@ 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, filename):
"Compiles template_string into NodeList ready for rendering" "Compiles template_string into NodeList ready for rendering"
tokens = tokenize(template_string) tokens = tokenize(template_string, filename)
parser = Parser(tokens) parser = Parser(tokens)
return parser.parse() return parser.parse()
@ -168,45 +176,70 @@ class Context:
self.dicts = [other_dict] + self.dicts self.dicts = [other_dict] + self.dicts
class Token: class Token:
def __init__(self, token_type, contents): def __init__(self, token_type, contents, source):
"The token_type must be TOKEN_TEXT, TOKEN_VAR or TOKEN_BLOCK" "The token_type must be TOKEN_TEXT, TOKEN_VAR or TOKEN_BLOCK"
self.token_type, self.contents = token_type, contents self.token_type, self.contents = token_type, contents
self.source = source
def __str__(self): def __str__(self):
return '<%s token: "%s...">' % ( return '<%s token: "%s..." from %s, line %d>' % (
{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', ''),
self.source[0], self.source[1]
) )
def tokenize(template_string):
def tokenize(template_string, filename):
"Return a list of tokens from a given template_string" "Return a list of tokens from a given template_string"
# remove all empty strings, because the regex has a tendency to add them # remove all empty strings, because the regex has a tendency to add them
bits = filter(None, tag_re.split(template_string)) linebreaks = [match.start() for match in newline_re.finditer(template_string)]
return map(create_token, bits) lastline = len(linebreaks)
token_tups = []
upto = 0
line = 1
def create_token(token_string):
for match in tag_re.finditer(template_string):
start, end = match.span()
if start > upto:
token_tups.append( (template_string[upto:start], line) )
upto = start
while linebreaks and line != lastline and linebreaks[line] <= upto:
line += 1
token_tups.append( (template_string[start:end], line) )
upto = end
while linebreaks and line != lastline and linebreaks[line] <= upto:
line += 1
return [ create_token(tok, (filename, line)) for tok, line in token_tups]
def create_token(token_string, source):
"Convert the given token string into a new Token object and return it" "Convert the given token string into a new Token object and return it"
if token_string.startswith(VARIABLE_TAG_START): if token_string.startswith(VARIABLE_TAG_START):
return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip(), source)
elif token_string.startswith(BLOCK_TAG_START): elif token_string.startswith(BLOCK_TAG_START):
return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip(), source)
else: else:
return Token(TOKEN_TEXT, token_string) return Token(TOKEN_TEXT, token_string, source)
class Parser: class Parser:
def __init__(self, tokens): def __init__(self, tokens):
self.tokens = tokens self.tokens = tokens
self.command_stack = []
def parse(self, parse_until=[]): def parse(self, parse_until=[]):
nodelist = NodeList() nodelist = 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)) nodelist.append(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" raise TemplateSyntaxError, "Empty variable tag at %s, line %d" % (token.source[0], token.source[1])
nodelist.append(VariableNode(token.contents)) nodelist.append(VariableNode(token.contents), 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
@ -218,11 +251,16 @@ class Parser:
raise TemplateSyntaxError, "Empty block tag" raise TemplateSyntaxError, "Empty block tag"
try: try:
# execute callback function for this tag and append resulting node # execute callback function for this tag and append resulting node
nodelist.append(registered_tags[command](self, token)) self.command_stack.append( (command, token.source) )
nodelist.append(registered_tags[command](self, token), token)
self.command_stack.pop()
except KeyError: except KeyError:
raise TemplateSyntaxError, "Invalid block tag: '%s'" % command raise TemplateSyntaxError, "Invalid block tag: '%s' at %s, line %d" % (command, token.source[0], token.source[1])
if parse_until: if parse_until:
raise TemplateSyntaxError, "Unclosed tag(s): '%s'" % ', '.join(parse_until) (command, (file,line)) = self.command_stack.pop()
msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \
(command, file, line, ', '.join(parse_until) )
raise TemplateSyntaxError, msg
return nodelist return nodelist
def next_token(self): def next_token(self):
@ -435,6 +473,7 @@ class Node:
nodes.extend(self.nodelist.get_nodes_by_type(nodetype)) nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
return nodes return nodes
class NodeList(list): class NodeList(list):
def render(self, context): def render(self, context):
bits = [] bits = []
@ -452,6 +491,11 @@ class NodeList(list):
nodes.extend(node.get_nodes_by_type(nodetype)) nodes.extend(node.get_nodes_by_type(nodetype))
return nodes return nodes
def append(self, node, token = None):
if token:
node.source = token.source
super(NodeList, self).append(node)
class TextNode(Node): class TextNode(Node):
def __init__(self, s): def __init__(self, s):
self.s = s self.s = s

View File

@ -4,14 +4,16 @@ from django.conf.settings import TEMPLATE_DIRS, TEMPLATE_FILE_EXTENSION
from django.core.template import TemplateDoesNotExist from django.core.template import TemplateDoesNotExist
import os import os
def load_template_source(template_name, template_dirs=None):
def find_template_source(template_name, template_dirs=None):
"Returns a tuple of (template_string, filepath)."
if not template_dirs: if not template_dirs:
template_dirs = TEMPLATE_DIRS template_dirs = TEMPLATE_DIRS
tried = [] tried = []
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:
@ -19,3 +21,7 @@ def load_template_source(template_name, template_dirs=None):
else: else:
error_msg = "Your TEMPLATE_DIRS settings is empty. Change it to point to at least one template directory." error_msg = "Your TEMPLATE_DIRS settings is empty. Change it to point to at least one template directory."
raise TemplateDoesNotExist, error_msg raise TemplateDoesNotExist, error_msg
def load_template_source(template_name, template_dirs=None):
return find_template_source(template_name, template_dirs)[0]

View File

@ -1,6 +1,6 @@
"Wrapper for loading templates from storage of some sort (e.g. files or db)" "Wrapper for loading templates from storage of some sort (e.g. files or db)"
import template import template
from template_file import load_template_source from template_file import find_template_source
class ExtendsError(Exception): class ExtendsError(Exception):
pass pass
@ -10,14 +10,14 @@ def get_template(template_name):
Returns a compiled template.Template object for the given template name, Returns a compiled template.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, filename=template.UNKNOWN_SOURCE):
""" """
Returns a compiled template.Template object for the given template code, Returns a compiled template.Template object for the given template code,
handling template inheritance recursively. handling template inheritance recursively.
""" """
return template.Template(source) return template.Template(source, filename)
def render_to_string(template_name, dictionary=None, context_instance=None): def render_to_string(template_name, dictionary=None, context_instance=None):
""" """
@ -90,7 +90,7 @@ class ExtendsNode(template.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 template.TemplateSyntaxError, error_msg raise template.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 template.TemplateDoesNotExist: except template.TemplateDoesNotExist:
raise template.TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent raise template.TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
@ -142,7 +142,7 @@ def do_extends(parser, token):
This tag may be used in two ways: ``{% extends "base" %}`` (with quotes) This tag may be used in two ways: ``{% extends "base" %}`` (with quotes)
uses the literal value "base" as the name of the parent template to extend, uses the literal value "base" as the name of the parent template to extend,
or ``{% entends variable %}`` uses the value of ``variable`` as the name or ``{% extends variable %}`` uses the value of ``variable`` as the name
of the parent template to extend. of the parent template to extend.
""" """
bits = token.contents.split() bits = token.contents.split()

View File

@ -573,7 +573,8 @@ class AdminBoundField(BoundField):
classes.append('nowrap') classes.append('nowrap')
if max([bool(f.errors()) for f in self.form_fields]): if max([bool(f.errors()) for f in self.form_fields]):
classes.append('error') classes.append('error')
self.cell_class_attribute = ' '.join(classes) if classes:
self.cell_class_attribute = ' class="%s" ' % ' '.join(classes)
self._repr_filled = False self._repr_filled = False