1
0
mirror of https://github.com/django/django.git synced 2025-07-05 10:19:20 +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 %}">
{% for bound_field in fcw.bound_fields %}
{% if not bound_field.not_in_table %}
<td "{{ bound_field.cell_class_attribute}}">
<td {{bound_field.cell_class_attribute}}>
{% field_widget bound_field %}
</td>
{% endif %}

View File

@ -1,7 +1,7 @@
<div class="submit-row">
{% 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_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 %}<input type="submit" value="Save" class="default" {{onclick_attrib}}/>{% 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_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 %}<input type="submit" value="Save" class="default" {{onclick_attrib}}/>
</div>

View File

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

View File

@ -74,10 +74,18 @@ VARIABLE_TAG_END = '}}'
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
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)))
# global dict used by register_tag; maps custom tags to callback functions
registered_tags = {}
@ -102,9 +110,9 @@ class SilentVariableFailure(Exception):
pass
class Template:
def __init__(self, template_string):
def __init__(self, template_string, filename=UNKNOWN_SOURCE):
"Compilation stage"
self.nodelist = compile_string(template_string)
self.nodelist = compile_string(template_string, filename)
def __iter__(self):
for node in self.nodelist:
@ -115,9 +123,9 @@ class Template:
"Display stage -- can be called many times"
return self.nodelist.render(context)
def compile_string(template_string):
def compile_string(template_string, filename):
"Compiles template_string into NodeList ready for rendering"
tokens = tokenize(template_string)
tokens = tokenize(template_string, filename)
parser = Parser(tokens)
return parser.parse()
@ -168,45 +176,70 @@ class Context:
self.dicts = [other_dict] + self.dicts
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"
self.token_type, self.contents = token_type, contents
self.source = source
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],
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"
# remove all empty strings, because the regex has a tendency to add them
bits = filter(None, tag_re.split(template_string))
return map(create_token, bits)
linebreaks = [match.start() for match in newline_re.finditer(template_string)]
lastline = len(linebreaks)
token_tups = []
upto = 0
line = 1
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):
def create_token(token_string, source):
"Convert the given token string into a new Token object and return it"
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):
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:
return Token(TOKEN_TEXT, token_string)
return Token(TOKEN_TEXT, token_string, source)
class Parser:
def __init__(self, tokens):
self.tokens = tokens
self.command_stack = []
def parse(self, parse_until=[]):
nodelist = NodeList()
while self.tokens:
token = self.next_token()
if token.token_type == TOKEN_TEXT:
nodelist.append(TextNode(token.contents))
nodelist.append(TextNode(token.contents), token)
elif token.token_type == TOKEN_VAR:
if not token.contents:
raise TemplateSyntaxError, "Empty variable tag"
nodelist.append(VariableNode(token.contents))
raise TemplateSyntaxError, "Empty variable tag at %s, line %d" % (token.source[0], token.source[1])
nodelist.append(VariableNode(token.contents), token)
elif token.token_type == TOKEN_BLOCK:
if token.contents in parse_until:
# put token back on token list so calling code knows why it terminated
@ -218,11 +251,16 @@ class Parser:
raise TemplateSyntaxError, "Empty block tag"
try:
# 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:
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:
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
def next_token(self):
@ -434,6 +472,7 @@ class Node:
if hasattr(self, 'nodelist'):
nodes.extend(self.nodelist.get_nodes_by_type(nodetype))
return nodes
class NodeList(list):
def render(self, context):
@ -451,6 +490,11 @@ class NodeList(list):
for node in self:
nodes.extend(node.get_nodes_by_type(nodetype))
return nodes
def append(self, node, token = None):
if token:
node.source = token.source
super(NodeList, self).append(node)
class TextNode(Node):
def __init__(self, s):

View File

@ -4,14 +4,16 @@ from django.conf.settings import TEMPLATE_DIRS, TEMPLATE_FILE_EXTENSION
from django.core.template import TemplateDoesNotExist
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:
template_dirs = TEMPLATE_DIRS
tried = []
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:
@ -19,3 +21,7 @@ def load_template_source(template_name, template_dirs=None):
else:
error_msg = "Your TEMPLATE_DIRS settings is empty. Change it to point to at least one template directory."
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)"
import template
from template_file import load_template_source
from template_file import find_template_source
class ExtendsError(Exception):
pass
@ -10,14 +10,14 @@ def get_template(template_name):
Returns a compiled template.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, filename=template.UNKNOWN_SOURCE):
"""
Returns a compiled template.Template object for the given template code,
handling template inheritance recursively.
"""
return template.Template(source)
return template.Template(source, filename)
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
raise template.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 template.TemplateDoesNotExist:
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)
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.
"""
bits = token.contents.split()

View File

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