Django

Code

Ticket #603: django-template-errors-2.patch

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

updated patch

  • 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() 
  • django/core/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] 
  • django/core/template.py

    old new  
    5555'\n<html>\n\n</html>\n' 
    5656""" 
    5757import re 
    58 from django.conf.settings import DEFAULT_CHARSET 
     58from django.conf.settings import DEFAULT_CHARSET, DEBUG 
    5959 
    6060__all__ = ('Template','Context','compile_string') 
    6161 
     
    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#match starts of lines 
     81newline_re = re.compile("^", re.M); 
     82 
    7783# match a variable or block tag and capture the entire tag, including start/end delimiters 
    7884tag_re = re.compile('(%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 
    7985                                          re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END))) 
     
    102108    pass 
    103109 
    104110class Template: 
    105     def __init__(self, template_string): 
     111    def __init__(self, template_string, filename=UNKNOWN_SOURCE): 
    106112        "Compilation stage" 
    107         self.nodelist = compile_string(template_string
    108  
     113        self.nodelist = compile_string(template_string, filename
     114        
    109115    def __iter__(self): 
    110116        for node in self.nodelist: 
    111117            for subnode in node: 
     
    115121        "Display stage -- can be called many times" 
    116122        return self.nodelist.render(context) 
    117123 
    118 def compile_string(template_string): 
     124def compile_string(template_string, filename): 
    119125    "Compiles template_string into NodeList ready for rendering" 
    120     tokens = tokenize(template_string) 
    121     parser = Parser(tokens) 
     126    if DEBUG: 
     127        lexer_factory = DebugLexer 
     128        parser_factory = DebugParser 
     129    else: 
     130        lexer_factory = Lexer 
     131        parser_factory = Parser 
     132         
     133    lexer = lexer_factory(template_string, filename) 
     134    parser = parser_factory(lexer.tokenize()) 
    122135    return parser.parse() 
    123136 
    124137class Context: 
     
    178191            self.contents[:20].replace('\n', '') 
    179192            ) 
    180193 
    181 def tokenize(template_string): 
    182     "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) 
     194class Lexer(object): 
     195    def __init__(self, template_string, filename): 
     196        self.template_string = template_string 
     197        self.filename = filename 
     198     
     199    def tokenize(self): 
     200        "Return a list of tokens from a given template_string" 
     201        bits = filter(None, tag_re.split(self.template_string)) 
     202        return map(self.create_token, bits) 
     203         
     204    def create_token(self,token_string): 
     205        "Convert the given token string into a new Token object and return it" 
     206        if token_string.startswith(VARIABLE_TAG_START): 
     207            token = Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) 
     208        elif token_string.startswith(BLOCK_TAG_START): 
     209            token = Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) 
     210        else: 
     211            token = Token(TOKEN_TEXT, token_string) 
     212        return token  
    186213 
    187 def create_token(token_string): 
    188     "Convert the given token string into a new Token object and return it" 
    189     if token_string.startswith(VARIABLE_TAG_START): 
    190         return Token(TOKEN_VAR, token_string[len(VARIABLE_TAG_START):-len(VARIABLE_TAG_END)].strip()) 
    191     elif token_string.startswith(BLOCK_TAG_START): 
    192         return Token(TOKEN_BLOCK, token_string[len(BLOCK_TAG_START):-len(BLOCK_TAG_END)].strip()) 
    193     else: 
    194         return Token(TOKEN_TEXT, token_string) 
     214class DebugLexer(Lexer): 
     215    def __init__(self, template_string, filename): 
     216        super(DebugLexer,self).__init__(template_string, filename) 
    195217 
    196 class Parser: 
     218    def find_linebreaks(self, template_string): 
     219        for match in newline_re.finditer(template_string): 
     220            yield match.start() 
     221 
     222    def tokenize(self): 
     223        "Return a list of tokens from a given template_string" 
     224         
     225        token_tups = [] 
     226        upto = 0 
     227        line = 1 
     228        #TODO:Py2.4 generator expression  
     229        linebreaks = self.find_linebreaks(self.template_string) 
     230        next_linebreak = linebreaks.next() 
     231        try: 
     232            for match in tag_re.finditer(self.template_string): 
     233                start, end = match.span() 
     234                if start > upto: 
     235                    token_tups.append( (self.template_string[upto:start], line) ) 
     236                    upto = start 
     237                     
     238                    while next_linebreak <= upto: 
     239                        next_linebreak = linebreaks.next() 
     240                        line += 1 
     241                 
     242                token_tups.append( (self.template_string[start:end], line) ) 
     243                upto = end 
     244         
     245                while next_linebreak <= upto: 
     246                    next_linebreak = linebreaks.next() 
     247                    line += 1 
     248        except StopIteration: 
     249            pass 
     250         
     251        last_bit = self.template_string[upto:] 
     252        if len(last_bit): 
     253           token_tups.append( (last_bit, line) ) 
     254         
     255        return [ self.create_token(tok, (self.filename, line)) for tok, line in token_tups] 
     256 
     257 
     258    def create_token(self, token_string, source): 
     259        token = super(DebugLexer, self).create_token(token_string) 
     260        token.source = source 
     261        return token 
     262 
     263 
     264class Parser(object): 
    197265    def __init__(self, tokens): 
    198266        self.tokens = tokens 
    199267 
     
    202270        while self.tokens: 
    203271            token = self.next_token() 
    204272            if token.token_type == TOKEN_TEXT: 
    205                 nodelist.append(TextNode(token.contents)
     273                self.extend_nodelist(nodelist, TextNode(token.contents), token
    206274            elif token.token_type == TOKEN_VAR: 
    207275                if not token.contents: 
    208                     raise TemplateSyntaxError, "Empty variable tag" 
    209                 nodelist.append(VariableNode(token.contents)
     276                    self.empty_variable(token) 
     277                self.extend_nodelist(nodelist, VariableNode(token.contents), token
    210278            elif token.token_type == TOKEN_BLOCK: 
    211279                if token.contents in parse_until: 
    212280                    # put token back on token list so calling code knows why it terminated 
     
    215283                try: 
    216284                    command = token.contents.split()[0] 
    217285                except IndexError: 
    218                     raise TemplateSyntaxError, "Empty block tag" 
     286                    self.empty_block_tag(token) 
     287                 
     288                # execute callback function for this tag and append resulting node 
     289                self.enter_command(command, token); 
    219290                try: 
    220                     # execute callback function for this tag and append resulting node 
    221                     nodelist.append(registered_tags[command](self, token)) 
     291                    compile_func = registered_tags[command] 
    222292                except KeyError: 
    223                     raise TemplateSyntaxError, "Invalid block tag: '%s'" % command 
     293                    self.invalid_block_tag(token, command) 
     294                     
     295                self.extend_nodelist(nodelist, compile_func(self, token), token) 
     296                self.exit_command(); 
     297                 
    224298        if parse_until: 
    225             raise TemplateSyntaxError, "Unclosed tag(s): '%s'" % ', '.join(parse_until) 
     299            self.unclosed_block_tag(token) 
     300             
    226301        return nodelist 
    227302 
     303    def extend_nodelist(self, nodelist, node, token): 
     304        nodelist.append(node) 
     305 
     306    def enter_command(self, command, token): 
     307        pass 
     308         
     309    def exit_command(self): 
     310        pass 
     311 
     312    def empty_variable(self, token): 
     313        raise TemplateSyntaxError, "Empty variable tag"  
     314     
     315    def empty_block_tag(self, token): 
     316        raise TemplateSyntaxError, "Empty block tag" 
     317     
     318    def invalid_block_tag(self, token, command): 
     319        raise TemplateSyntaxError, "Invalid block tag: %s" % (command) 
     320     
     321    def unclosed_block_tag(self, token): 
     322        raise TemplateSyntaxError, "Unclosed tags: %s " %  ', '.join(parse_until) 
     323         
    228324    def next_token(self): 
    229325        return self.tokens.pop(0) 
    230326 
     
    234330    def delete_first_token(self): 
    235331        del self.tokens[0] 
    236332 
     333 
     334class DebugParser(Parser): 
     335    def __init__(self, lexer): 
     336        super(DebugParser, self).__init__(lexer) 
     337        self.command_stack = [] 
     338 
     339    def enter_command(self, command, token): 
     340        self.command_stack.append( (command, token.source) ) 
     341         
     342    def exit_command(self): 
     343        self.command_stack.pop() 
     344 
     345    def format_source(self, source): 
     346        return "at %s, line %d" % source 
     347 
     348    def extend_nodelist(self, nodelist, node, token): 
     349        node.source = token.source 
     350        super(DebugParser, self).extend_nodelist(nodelist, node, token) 
     351 
     352    def empty_variable(self, token): 
     353        raise TemplateSyntaxError, "Empty variable tag %s" % self.format_source(token.source) 
     354     
     355    def empty_block_tag(self, token): 
     356        raise TemplateSyntaxError, "Empty block tag %s" % self.format_source(token.source) 
     357     
     358    def invalid_block_tag(self, token, command): 
     359        raise TemplateSyntaxError, "Invalid block tag: '%s' %s" % (command, self.format_source(token.source)) 
     360     
     361    def unclosed_block_tag(self, token): 
     362        (command, (file,line)) = self.command_stack.pop() 
     363        msg = "Unclosed tag '%s' starting at %s, line %d. Looking for one of: %s " % \ 
     364              (command, file, line, ', '.join(parse_until) )  
     365        raise TemplateSyntaxError, msg 
     366 
    237367class FilterParser: 
    238368    """Parse a variable token and its optional filters (all as a single string), 
    239369       and return a list of tuples of the filter name and arguments.