Django

Code

Ticket #959: filters-multi-args.patch

File filters-multi-args.patch, 14.1 kB (added by rjwittams, 3 years ago)
  • django/core/template/defaultfilters.py

    old new  
    178178    "Converts newlines into <br />s" 
    179179    return value.replace('\n', '<br />') 
    180180 
    181 def removetags(value, tags): 
    182     "Removes a space separated list of [X]HTML tags from the output" 
    183     tags = [re.escape(tag) for tag in tags.split()
     181def removetags(value, *tags): 
     182    "Removes a list of [X]HTML tags from the output" 
     183    tags = [re.escape(tag) for tag in tags
    184184    tags_re = '(%s)' % '|'.join(tags) 
    185185    starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re) 
    186186    endtag_re = re.compile('</%s>' % tags_re) 
     
    196196    return strip_tags(value) 
    197197 
    198198################### 
     199# COLLECTIONS     # 
     200################### 
     201 
     202def lookup(value, arg): 
     203    return value[arg] 
     204 
     205################### 
    199206# LISTS           # 
    200207################### 
    201208 
     
    383390# MISC            # 
    384391################### 
    385392 
    386 def filesizeformat(bytes): 
     393def filesizeformat(bytes, format=None): 
    387394    """ 
    388395    Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102 
    389396    bytes, etc). 
    390397    """ 
     398    base = format=='iso' and 1000 or 1024 
    391399    bytes = float(bytes) 
    392     if bytes < 1024
     400    if bytes < base
    393401        return "%d byte%s" % (bytes, bytes != 1 and 's' or '') 
    394     if bytes < 1024 * 1024
     402    if bytes < base * base
    395403        return "%.1f KB" % (bytes / 1024) 
    396     if bytes < 1024 * 1024 * 1024
    397         return "%.1f MB" % (bytes / (1024 * 1024)) 
    398     return "%.1f GB" % (bytes / (1024 * 1024 * 1024)) 
     404    if bytes < base * base * base
     405        return "%.1f MB" % (bytes / (base * base)) 
     406    return "%.1f GB" % (bytes / (base * base * base)) 
    399407 
    400408def pluralize(value): 
    401409    "Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'" 
     
    423431    return pformat(value) 
    424432 
    425433# Syntax: register.filter(name of filter, callback) 
     434 
    426435register.filter(add) 
    427436register.filter(addslashes) 
    428437register.filter(capfirst) 
     
    447456register.filter(linebreaksbr) 
    448457register.filter(linenumbers) 
    449458register.filter(ljust) 
     459register.filter(lookup) 
    450460register.filter(lower) 
    451461register.filter(make_list) 
    452462register.filter(phone2numeric) 
  • django/core/template/__init__.py

    old new  
    6767 
    6868# template syntax constants 
    6969FILTER_SEPARATOR = '|' 
    70 FILTER_ARGUMENT_SEPARATOR = ':' 
     70FILTER_ARGUMENT_INTRODUCTION = ':' 
     71FILTER_ARGUMENT_SEPARATOR = ',' 
    7172VARIABLE_ATTRIBUTE_SEPARATOR = '.' 
    7273BLOCK_TAG_START = '{%' 
    7374BLOCK_TAG_END = '%}' 
     
    494495            self.pointer = i 
    495496            return s 
    496497 
     498def prepare_filter_res(): 
     499        re_args = { 
     500        'str': r"""[^"\\]*(?:\\.[^"\\]*)*""", 
     501        'var_chars': "A-Za-z0-9\_\." , 
     502        'filter_sep': re.escape(FILTER_SEPARATOR), 
     503        'arg_intro': re.escape(FILTER_ARGUMENT_INTRODUCTION), 
     504        'arg_sep' : re.escape(FILTER_ARGUMENT_SEPARATOR),  
     505        'i18n_open' : re.escape("_("), 
     506        'i18n_close' : re.escape(")"), 
     507        } 
     508        
     509        opening_rs = r""" 
     510        ^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s| 
     511        ^"(?P<constant>%(str)s)"| 
     512        ^(?P<var>[%(var_chars)s]+) 
     513        """ 
     514     
     515        filter_name_rs = r""" 
     516        ^%(filter_sep)s(?P<filter_name>\w+) 
     517        """ 
     518  
     519        arg_common = r""" 
     520         (?: 
     521          %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s| 
     522          "(?P<constant_arg>%(str)s)"| 
     523          (?P<var_arg>[%(var_chars)s]+) 
     524         ) 
     525        """ 
     526         
     527        first_arg_rs = r"""^%(arg_intro)s""" + arg_common 
     528        further_arg_rs = r"""^%(arg_sep)s""" + arg_common 
     529     
     530        raw_strings = [opening_rs, filter_name_rs, first_arg_rs, further_arg_rs] 
     531        return [re.compile((s % re_args).replace("\n", "").replace(" ", "")) for s in raw_strings]  
    497532 
    498  
    499  
    500 filter_raw_string = r""" 
    501 ^%(i18n_open)s"(?P<i18n_constant>%(str)s)"%(i18n_close)s| 
    502 ^"(?P<constant>%(str)s)"| 
    503 ^(?P<var>[%(var_chars)s]+)| 
    504  (?:%(filter_sep)s 
    505      (?P<filter_name>\w+) 
    506          (?:%(arg_sep)s 
    507              (?: 
    508               %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s| 
    509               "(?P<constant_arg>%(str)s)"| 
    510               (?P<var_arg>[%(var_chars)s]+) 
    511              ) 
    512          )? 
    513  )""" % { 
    514     'str': r"""[^"\\]*(?:\\.[^"\\]*)*""", 
    515     'var_chars': "A-Za-z0-9\_\." , 
    516     'filter_sep': re.escape(FILTER_SEPARATOR), 
    517     'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR), 
    518     'i18n_open' : re.escape("_("), 
    519     'i18n_close' : re.escape(")"), 
    520   } 
    521  
    522 filter_raw_string = filter_raw_string.replace("\n", "").replace(" ", "") 
    523 filter_re = re.compile(filter_raw_string) 
    524  
    525533class FilterExpression(object): 
    526534    """ 
    527535    Parses a variable token and its optional filters (all as a single string), 
    528     and return a list of tuples of the filter name and arguments. 
     536    and return a list of tuples of the filter function and arguments. 
    529537    Sample: 
    530538        >>> token = 'variable|default:"Default value"|date:"Y-m-d"' 
    531539        >>> p = FilterParser(token) 
     
    537545    This class should never be instantiated outside of the 
    538546    get_filters_from_token helper function. 
    539547    """ 
     548     
     549    opening_re, filter_name_re, first_arg_re, further_arg_re = prepare_filter_res() 
     550     
     551     
    540552    def __init__(self, token, parser): 
    541553        self.token = token 
    542         matches = filter_re.finditer(token) 
    543554        var = None 
    544555        filters = [] 
    545         upto = 0 
    546         for match in matches: 
    547             start = match.start() 
    548             if upto != start: 
    549                 raise TemplateSyntaxError, "Could not parse some characters: %s|%s|%s"  % \ 
    550                                            (token[:upto], token[upto:start], token[start:]) 
     556         
     557        remainder = token 
     558        # match an opening 
     559        match = FilterExpression.opening_re.match(remainder) 
     560        if match: 
     561            var, constant, i18n_constant = match.group("var", "constant", "i18n_constant") 
     562            if var and (var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_'): 
     563                raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % var 
     564             
     565            if i18n_constant: 
     566                var = '"%s"' %  _(i18n_constant.replace('\\', '') ) 
     567            elif constant: 
     568                var = '"%s"' % constant.replace('\\','') 
     569             
     570            remainder = remainder[match.end():] 
    551571            if var == None: 
    552                 var, constant, i18n_constant = match.group("var", "constant", "i18n_constant") 
    553                 if i18n_constant: 
    554                     var = '"%s"' %  _(i18n_constant) 
    555                 elif constant: 
    556                     var = '"%s"' % constant 
    557                 upto = match.end() 
    558                 if var == None: 
    559                     raise TemplateSyntaxError, "Could not find variable at start of %s" % token 
    560                 elif var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_': 
    561                     raise TemplateSyntaxError, "Variables and attributes may not begin with underscores: '%s'" % var 
    562             else: 
    563                 filter_name = match.group("filter_name") 
    564                 args = [] 
    565                 constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg") 
    566                 if i18n_arg: 
    567                     args.append((False, _(i18n_arg.replace('\\', '')))) 
    568                 elif constant_arg: 
    569                     args.append((False, constant_arg.replace('\\', ''))) 
    570                 elif var_arg: 
    571                     args.append((True, var_arg)) 
    572                 filter_func = parser.find_filter(filter_name) 
    573                 self.args_check(filter_name,filter_func, args) 
    574                 filters.append( (filter_func,args)) 
    575                 upto = match.end() 
    576         if upto != len(token): 
    577             raise TemplateSyntaxError, "Could not parse the remainder: %s" % token[upto:] 
     572                raise TemplateSyntaxError, "Could not find variable at start of %s" % token 
     573        else: 
     574            raise TemplateSyntaxError, "Could not find variable at start of |%s|" % token 
     575         
     576        #match a number of filters 
     577        # first, maybe match a filter name  
     578        match = FilterExpression.filter_name_re.match(remainder) 
     579        while match: 
     580            filter_name = match.group("filter_name") 
     581            filter_func = parser.find_filter(filter_name) 
     582            remainder = remainder[match.end():] 
     583            args = [] 
     584            # maybe match a first argument 
     585            match = FilterExpression.first_arg_re.match(remainder) 
     586            if match: 
     587             
     588                args.append(self.getarg(match)) 
     589                remainder = remainder[match.end():] 
     590                # match a number of further arguments 
     591                match = FilterExpression.further_arg_re.match(remainder) 
     592                while match: 
     593                    args.append(self.getarg(match)) 
     594                    remainder = remainder[match.end():] 
     595                    match = FilterExpression.further_arg_re.match(remainder) 
     596            self.args_check(filter_name,filter_func, args) 
     597            filters.append((filter_func, args))     
     598            # maybe match another filter name 
     599            match = FilterExpression.filter_name_re.match(remainder) 
     600         
     601        if len(remainder) != 0: 
     602            raise TemplateSyntaxError, "Could not parse the remainder of the filter expression: %s" % remainder 
    578603        self.var , self.filters = var, filters 
    579604 
     605    def getarg(self, match): 
     606        constant_arg, i18n_arg, var_arg = match.group("constant_arg", "i18n_arg", "var_arg") 
     607        if i18n_arg: 
     608            return (False, _(i18n_arg.replace('\\', ''))) 
     609        elif constant_arg: 
     610            return (False, constant_arg.replace('\\', '')) 
     611        elif var_arg: 
     612            return (True, var_arg) 
     613 
    580614    def resolve(self, context): 
    581615        try: 
    582616            obj = resolve_variable(self.var, context) 
     
    602636            nondefs = args[:-len(defaults)] 
    603637        else: 
    604638            nondefs = args 
     639         
    605640        # Args without defaults must be provided. 
    606641        try: 
    607642            for arg in nondefs: 
     
    610645            # Not enough 
    611646            raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen) 
    612647 
    613         # Defaults can be overridden. 
    614         defaults = defaults and list(defaults) or [] 
    615         try: 
    616             for parg in provided: 
    617                 defaults.pop(0) 
    618         except IndexError: 
    619             # Too many. 
    620             raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen) 
     648        #if varargs is true, it will eat up the rest of the args.  
     649        if not varargs: 
     650            # Defaults can be overridden. 
     651            defaults = defaults and list(defaults) or [] 
     652            try: 
     653                for parg in provided: 
     654                    defaults.pop(0) 
     655            except IndexError: 
     656                # Too many. 
     657                raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen) 
    621658 
    622659        return True 
    623660    args_check = staticmethod(args_check) 
     
    629666    """ 
    630667    Returns the resolved variable, which may contain attribute syntax, within 
    631668    the given context. The variable may be a hard-coded string (if it begins 
    632     and ends with single or double quote marks)
     669    and ends with single or double quote marks), or an integer or float literal
    633670 
    634671    >>> c = {'article': {'section':'News'}} 
    635672    >>> resolve_variable('article.section', c) 
     
    645682 
    646683    (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.') 
    647684    """ 
    648     if path[0] in ('"', "'") and path[0] == path[-1]: 
     685    if path[0] in '0123456789': 
     686        number_type = '.' in path and float or int 
     687        try: 
     688           current = number_type(path) 
     689        except ValueError: 
     690           current = '' 
     691    elif path[0] in ('"', "'") and path[0] == path[-1]: 
    649692        current = path[1:-1] 
    650693    else: 
    651694        current = context 
  • tests/othertests/templates.py

    old new  
    101101    'basic-syntax28': ("{% %}", {}, template.TemplateSyntaxError), 
    102102 
    103103    # Chained filters, with an argument to the first one 
    104     'basic-syntax29': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"), 
     104    'basic-syntax29': ('{{ var|removetags:"b","i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"), 
    105105 
    106106    # Escaped string as argument 
    107107    'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'), 
     
    112112    # Default argument testing 
    113113    'basic-syntax32' : (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'), 
    114114 
     115    # Lookup 
     116    'basic-syntax33' : (r'{{ nicks|lookup:"robert"}} {{nicks|lookup:lead}}',  
     117                         {'nicks': {'robert': 'rjwittams', 'adrian': 'adrian_h'},  
     118                                    'lead': 'adrian', }, 'rjwittams adrian_h'  ), 
     119                                     
     120    #Numbers  
     121    'basic-syntax34' : (r'{{ 1|add:2 }} {{1.0|add:2.0}}', {}, '3 3'), 
     122 
    115123    ### IF TAG ################################################################ 
    116124    'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"), 
    117125    'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"), 
     
    347355        raise Exception, msg 
    348356 
    349357if __name__ == "__main__": 
     358    template.libraries['django.templatetags.testtags'] = \ 
     359       template.get_library('tests.testapp.templatetags.testtags') 
    350360    run_tests(1, True)