Ticket #959: filters-multi-args.patch
File filters-multi-args.patch, 14.1 KB (added by , 19 years ago) |
---|
-
django/core/template/defaultfilters.py
=== django/core/template/defaultfilters.py ==================================================================
178 178 "Converts newlines into <br />s" 179 179 return value.replace('\n', '<br />') 180 180 181 def removetags(value, tags):182 "Removes a space separatedlist of [X]HTML tags from the output"183 tags = [re.escape(tag) for tag in tags .split()]181 def removetags(value, *tags): 182 "Removes a list of [X]HTML tags from the output" 183 tags = [re.escape(tag) for tag in tags] 184 184 tags_re = '(%s)' % '|'.join(tags) 185 185 starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re) 186 186 endtag_re = re.compile('</%s>' % tags_re) … … 196 196 return strip_tags(value) 197 197 198 198 ################### 199 # COLLECTIONS # 200 ################### 201 202 def lookup(value, arg): 203 return value[arg] 204 205 ################### 199 206 # LISTS # 200 207 ################### 201 208 … … 383 390 # MISC # 384 391 ################### 385 392 386 def filesizeformat(bytes ):393 def filesizeformat(bytes, format=None): 387 394 """ 388 395 Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102 389 396 bytes, etc). 390 397 """ 398 base = format=='iso' and 1000 or 1024 391 399 bytes = float(bytes) 392 if bytes < 1024:400 if bytes < base: 393 401 return "%d byte%s" % (bytes, bytes != 1 and 's' or '') 394 if bytes < 1024 * 1024:402 if bytes < base * base: 395 403 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)) 399 407 400 408 def pluralize(value): 401 409 "Returns 's' if the value is not 1, for '1 vote' vs. '2 votes'" … … 423 431 return pformat(value) 424 432 425 433 # Syntax: register.filter(name of filter, callback) 434 426 435 register.filter(add) 427 436 register.filter(addslashes) 428 437 register.filter(capfirst) … … 447 456 register.filter(linebreaksbr) 448 457 register.filter(linenumbers) 449 458 register.filter(ljust) 459 register.filter(lookup) 450 460 register.filter(lower) 451 461 register.filter(make_list) 452 462 register.filter(phone2numeric) -
django/core/template/__init__.py
=== django/core/template/__init__.py ==================================================================
67 67 68 68 # template syntax constants 69 69 FILTER_SEPARATOR = '|' 70 FILTER_ARGUMENT_SEPARATOR = ':' 70 FILTER_ARGUMENT_INTRODUCTION = ':' 71 FILTER_ARGUMENT_SEPARATOR = ',' 71 72 VARIABLE_ATTRIBUTE_SEPARATOR = '.' 72 73 BLOCK_TAG_START = '{%' 73 74 BLOCK_TAG_END = '%}' … … 494 495 self.pointer = i 495 496 return s 496 497 498 def 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] 497 532 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)s505 (?P<filter_name>\w+)506 (?:%(arg_sep)s507 (?: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 525 533 class FilterExpression(object): 526 534 """ 527 535 Parses a variable token and its optional filters (all as a single string), 528 and return a list of tuples of the filter nameand arguments.536 and return a list of tuples of the filter function and arguments. 529 537 Sample: 530 538 >>> token = 'variable|default:"Default value"|date:"Y-m-d"' 531 539 >>> p = FilterParser(token) … … 537 545 This class should never be instantiated outside of the 538 546 get_filters_from_token helper function. 539 547 """ 548 549 opening_re, filter_name_re, first_arg_re, further_arg_re = prepare_filter_res() 550 551 540 552 def __init__(self, token, parser): 541 553 self.token = token 542 matches = filter_re.finditer(token)543 554 var = None 544 555 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():] 551 571 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 578 603 self.var , self.filters = var, filters 579 604 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 580 614 def resolve(self, context): 581 615 try: 582 616 obj = resolve_variable(self.var, context) … … 602 636 nondefs = args[:-len(defaults)] 603 637 else: 604 638 nondefs = args 639 605 640 # Args without defaults must be provided. 606 641 try: 607 642 for arg in nondefs: … … 610 645 # Not enough 611 646 raise TemplateSyntaxError, "%s requires %d arguments, %d provided" % (name, len(nondefs), plen) 612 647 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) 621 658 622 659 return True 623 660 args_check = staticmethod(args_check) … … 629 666 """ 630 667 Returns the resolved variable, which may contain attribute syntax, within 631 668 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. 633 670 634 671 >>> c = {'article': {'section':'News'}} 635 672 >>> resolve_variable('article.section', c) … … 645 682 646 683 (The example assumes VARIABLE_ATTRIBUTE_SEPARATOR is '.') 647 684 """ 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]: 649 692 current = path[1:-1] 650 693 else: 651 694 current = context -
tests/othertests/templates.py
=== tests/othertests/templates.py ==================================================================
101 101 'basic-syntax28': ("{% %}", {}, template.TemplateSyntaxError), 102 102 103 103 # Chained filters, with an argument to the first one 104 'basic-syntax29': ('{{ var|removetags:"b 104 'basic-syntax29': ('{{ var|removetags:"b","i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"), 105 105 106 106 # Escaped string as argument 107 107 'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'), … … 112 112 # Default argument testing 113 113 'basic-syntax32' : (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'), 114 114 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 115 123 ### IF TAG ################################################################ 116 124 'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"), 117 125 'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"), … … 347 355 raise Exception, msg 348 356 349 357 if __name__ == "__main__": 358 template.libraries['django.templatetags.testtags'] = \ 359 template.get_library('tests.testapp.templatetags.testtags') 350 360 run_tests(1, True)