Django

Code

Ticket #603: django-template-errors.patch

File django-template-errors.patch, 10.6 kB (added by rjwittams, 3 years ago)

patch. Apply in the directory django/core/

  • template_loader.py

    old new  
    11"Wrapper for loading templates from storage of some sort (e.g. files or db)" 
    22import template 
    3 from template_file import load_template_source 
     3from template_file import find_template_source 
    44 
    55class ExtendsError(Exception): 
    66    pass 
     
    1010    Returns a compiled template.Template object for the given template name, 
    1111    handling template inheritance recursively. 
    1212    """ 
    13     return get_template_from_string(load_template_source(template_name)) 
     13    return get_template_from_string(*find_template_source(template_name)) 
    1414 
    15 def get_template_from_string(source): 
     15def get_template_from_string(source, filename=template.UNKNOWN_SOURCE): 
    1616    """ 
    1717    Returns a compiled template.Template object for the given template code, 
    1818    handling template inheritance recursively. 
    1919    """ 
    20     return template.Template(source
     20    return template.Template(source, filename
    2121 
    2222def render_to_string(template_name, dictionary=None, context_instance=None): 
    2323    """ 
     
    9090                error_msg += " Got this from the %r variable." % self.parent_name_var 
    9191            raise template.TemplateSyntaxError, error_msg 
    9292        try: 
    93             return get_template_from_string(load_template_source(parent, self.template_dirs)) 
     93            return get_template_from_string(*find_template_source(parent, self.template_dirs)) 
    9494        except template.TemplateDoesNotExist: 
    9595            raise template.TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent 
    9696 
     
    142142 
    143143    This tag may be used in two ways: ``{% extends "base" %}`` (with quotes) 
    144144    uses the literal value "base" as the name of the parent template to extend, 
    145     or ``{% entends variable %}`` uses the value of ``variable`` as the name 
     145    or ``{% extends variable %}`` uses the value of ``variable`` as the name 
    146146    of the parent template to extend. 
    147147    """ 
    148148    bits = token.contents.split() 
  • template_file.py

    old new  
    44from django.core.template import TemplateDoesNotExist 
    55import os 
    66 
    7 def load_template_source(template_name, template_dirs=None): 
     7 
     8def find_template_source(template_name, template_dirs=None): 
     9    "Returns a tuple of (template_string, filepath)." 
    810    if not template_dirs: 
    911        template_dirs = TEMPLATE_DIRS 
    1012    tried = [] 
    1113    for template_dir in template_dirs: 
    1214        filepath = os.path.join(template_dir, template_name) + TEMPLATE_FILE_EXTENSION 
    1315        try: 
    14             return open(filepath).read(
     16            return (open(filepath).read(), filepath
    1517        except IOError: 
    1618            tried.append(filepath) 
    1719    if template_dirs: 
     
    1921    else: 
    2022        error_msg = "Your TEMPLATE_DIRS settings is empty. Change it to point to at least one template directory." 
    2123    raise TemplateDoesNotExist, error_msg 
     24 
     25 
     26def load_template_source(template_name, template_dirs=None): 
     27    return find_template_source(template_name, template_dirs)[0] 
  • template.py

    old new  
    7474 
    7575ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.' 
    7676 
     77#What to report as the origin of templates that come from non file sources (eg strings) 
     78UNKNOWN_SOURCE="<unknown source>" 
     79 
     80 
     81#match starts of lines 
     82newline_re = re.compile("^", re.M); 
     83 
    7784# match a variable or block tag and capture the entire tag, including start/end delimiters 
    7885tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 
    7986                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END))) 
    8087 
     88 
    8189# global dict used by register_tag; maps custom tags to callback functions 
    8290registered_tags = {} 
    8391 
     
    102110    pass 
    103111 
    104112class Template: 
    105     def __init__(self, template_string): 
     113    def __init__(self, template_string, filename=UNKNOWN_SOURCE): 
    106114        "Compilation stage" 
    107         self.nodelist = compile_string(template_string
    108  
     115        self.nodelist = compile_string(template_string, filename
     116        
    109117    def __iter__(self): 
    110118        for node in self.nodelist: 
    111119            for subnode in node: 
     
    115123        "Display stage -- can be called many times" 
    116124        return self.nodelist.render(context) 
    117125 
    118 def compile_string(template_string): 
     126def compile_string(template_string, filename): 
    119127    "Compiles template_string into NodeList ready for rendering" 
    120     tokens = tokenize(template_string
     128    tokens = tokenize(template_string, filename
    121129    parser = Parser(tokens) 
    122130    return parser.parse() 
    123131 
     
    168176        self.dicts = [other_dict] + self.dicts 
    169177 
    170178class Token: 
    171     def __init__(self, token_type, contents): 
     179    def __init__(self, token_type, contents, source): 
    172180        "The token_type must be TOKEN_TEXT, TOKEN_VAR or TOKEN_BLOCK" 
    173181        self.token_type, self.contents = token_type, contents 
     182        self.source = source 
    174183 
    175184    def __str__(self): 
    176         return '<%s token: "%s...">' % ( 
     185        return '<%s token: "%s..." from %s, line %d>' % ( 
    177186            {TOKEN_TEXT:'Text', TOKEN_VAR:'Var', TOKEN_BLOCK:'Block'}[self.token_type], 
    178             self.contents[:20].replace('\n', '') 
     187            self.contents[:20].replace('\n', ''),  
     188            self.source[0], self.source[1] 
    179189            ) 
    180190 
    181 def tokenize(template_string): 
     191def find_linebreaks(template_string): 
     192    for match in newline_re.finditer(template_string): 
     193        yield match.start() 
     194 
     195def tokenize(template_string, filename): 
    182196    "Return a list of tokens from a given template_string" 
    183     # remove all empty strings, because the regex has a tendency to add them 
    184     bits = filter(None, tag_re.split(template_string)) 
    185     return map(create_token, bits) 
     197     
     198    token_tups = [] 
     199    upto = 0 
     200    line = 1 
     201    #TODO:Py2.4 generator expression  
     202    linebreaks = find_linebreaks(template_string) 
     203    next_linebreak = linebreaks.next() 
     204    try: 
     205        for match in tag_re.finditer(template_string): 
     206            start, end = match.span() 
     207            if start > upto: 
     208                token_tups.append( (template_string[upto:start], line) ) 
     209                upto = start 
     210                 
     211                while next_linebreak <= upto: 
     212                    next_linebreak = linebreaks.next() 
     213                    line += 1 
     214             
     215            token_tups.append( (template_string[start:end], line) ) 
     216            upto = end 
     217     
     218            while next_linebreak <= upto: 
     219                next_linebreak = linebreaks.next() 
     220                line += 1 
     221    except StopIteration: 
     222        pass 
     223     
     224    last_bit = template_string[upto:] 
     225    if len(last_bit): 
     226       token_tups.append( (last_bit, line) ) 
     227     
     228    return [ create_token(tok, (filename, line)) for tok, line in token_tups] 
    186229 
    187 def create_token(token_string): 
     230def create_token(token_string, source): 
    188231    "Convert the given token string into a new Token object and return it" 
    189232    if token_string.startswith(VARIABLE_TAG_START): 
    190         return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()
     233        return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip(), source
    191234    elif token_string.startswith(BLOCK_TAG_START): 
    192         return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()
     235        return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip(), source
    193236    else: 
    194         return Token(TOKEN_TEXT, token_string
     237        return Token(TOKEN_TEXT, token_string, source
    195238 
     239 
    196240class Parser: 
    197241    def __init__(self, tokens): 
    198242        self.tokens = tokens 
     243        self.command_stack = [] 
    199244 
    200245    def parse(self, parse_until=[]): 
    201246        nodelist = NodeList() 
    202247        while self.tokens: 
    203248            token = self.next_token() 
    204249            if token.token_type == TOKEN_TEXT: 
    205                 nodelist.append(TextNode(token.contents)
     250                nodelist.append(TextNode(token.contents), token
    206251            elif token.token_type == TOKEN_VAR: 
    207252                if not token.contents: 
    208                     raise TemplateSyntaxError, "Empty variable tag" 
    209                 nodelist.append(VariableNode(token.contents)
     253                    raise TemplateSyntaxError, "Empty variable tag at %s, line %d" % (token.source[0], token.source[1]) 
     254                nodelist.append(VariableNode(token.contents), token
    210255            elif token.token_type == TOKEN_BLOCK: 
    211256                if token.contents in parse_until: 
    212257                    # put token back on token list so calling code knows why it terminated 
     
    218263                    raise TemplateSyntaxError, "Empty block tag" 
    219264                try: 
    220265                    # execute callback function for this tag and append resulting node 
    221                     nodelist.append(registered_tags[command](self, token)) 
     266                    self.command_stack.append( (command, token.source) ) 
     267                    nodelist.append(registered_tags[command](self, token), token) 
     268                    self.command_stack.pop() 
    222269                except KeyError: 
    223                     raise TemplateSyntaxError, "Invalid block tag: '%s'" % command 
     270                    raise TemplateSyntaxError, "Invalid block tag: '%s' at %s, line %d" % (command, token.source[0], token.source[1]) 
    224271        if parse_until: 
    225             raise TemplateSyntaxError, "Unclosed tag(s): '%s'" % ', '.join(parse_until) 
     272            (command, (file,line)) = self.command_stack.pop() 
     273            msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \ 
     274                  (command, file, line, ', '.join(parse_until) )  
     275            raise TemplateSyntaxError, msg 
    226276        return nodelist 
    227277 
    228278    def next_token(self): 
     
    434484        if hasattr(self, 'nodelist'): 
    435485            nodes.extend(self.nodelist.get_nodes_by_type(nodetype)) 
    436486        return nodes 
     487     
    437488 
    438489class NodeList(list): 
    439490    def render(self, context): 
     
    451502        for node in self: 
    452503            nodes.extend(node.get_nodes_by_type(nodetype)) 
    453504        return nodes 
     505     
     506    def append(self, node, token = None): 
     507        if token: 
     508            node.source = token.source 
     509        super(NodeList, self).append(node) 
    454510 
    455511class TextNode(Node): 
    456512    def __init__(self, s):