mirror of
https://github.com/django/django.git
synced 2025-07-05 18:29:11 +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:
parent
f9e1deb8b3
commit
c7d7e4cb78
@ -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 %}
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
|
||||
|
||||
|
@ -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):
|
||||
|
@ -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]
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user