Ticket #13956: 13956.ttag_helpers_args_kwargs_support.2.diff
File 13956.ttag_helpers_args_kwargs_support.2.diff, 73.2 KB (added by , 13 years ago) |
---|
-
django/template/base.py
diff --git a/django/template/base.py b/django/template/base.py index c94eeb5..6fad075 100644
a b TOKEN_TEXT = 0 19 19 TOKEN_VAR = 1 20 20 TOKEN_BLOCK = 2 21 21 TOKEN_COMMENT = 3 22 TOKEN_MAPPING = { 23 TOKEN_TEXT: 'Text', 24 TOKEN_VAR: 'Var', 25 TOKEN_BLOCK: 'Block', 26 TOKEN_COMMENT: 'Comment', 27 } 22 28 23 29 # template syntax constants 24 30 FILTER_SEPARATOR = '|' … … ALLOWED_VARIABLE_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01 41 47 UNKNOWN_SOURCE = '<unknown source>' 42 48 43 49 # match a variable or block tag and capture the entire tag, including start/end delimiters 44 tag_re = re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 45 re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END), 46 re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END))) 50 tag_re = (re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % 51 (re.escape(BLOCK_TAG_START), re.escape(BLOCK_TAG_END), 52 re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END), 53 re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))) 47 54 48 55 # global dictionary of libraries that have been loaded using get_library 49 56 libraries = {} … … class VariableDoesNotExist(Exception): 73 80 return unicode(self).encode('utf-8') 74 81 75 82 def __unicode__(self): 76 return self.msg % tuple([force_unicode(p, errors='replace') for p in self.params]) 83 return self.msg % tuple([force_unicode(p, errors='replace') 84 for p in self.params]) 77 85 78 86 class InvalidTemplateLibrary(Exception): 79 87 pass … … class Template(object): 101 109 try: 102 110 template_string = smart_unicode(template_string) 103 111 except UnicodeDecodeError: 104 raise TemplateEncodingError("Templates can only be constructed from unicode or UTF-8 strings.") 112 raise TemplateEncodingError("Templates can only be constructed " 113 "from unicode or UTF-8 strings.") 105 114 if settings.TEMPLATE_DEBUG and origin is None: 106 115 origin = StringOrigin(template_string) 107 116 self.nodelist = compile_string(template_string, origin) … … class Token(object): 141 150 self.lineno = None 142 151 143 152 def __str__(self): 144 return '<%s token: "%s...">' % \145 ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],146 self.contents[:20].replace('\n', ''))153 token_name = TOKEN_MAPPING[self.token_type] 154 return ('<%s token: "%s...">' % 155 (token_name, self.contents[:20].replace('\n', ''))) 147 156 148 157 def split_contents(self): 149 158 split = [] … … class Parser(object): 207 216 self.add_library(lib) 208 217 209 218 def parse(self, parse_until=None): 210 if parse_until is None: parse_until = [] 219 if parse_until is None: 220 parse_until = [] 211 221 nodelist = self.create_nodelist() 212 222 while self.tokens: 213 223 token = self.next_token() … … class Parser(object): 218 228 self.empty_variable(token) 219 229 filter_expression = self.compile_filter(token.contents) 220 230 var_node = self.create_variable_node(filter_expression) 221 self.extend_nodelist(nodelist, var_node, token)231 self.extend_nodelist(nodelist, var_node, token) 222 232 elif token.token_type == TOKEN_BLOCK: 223 233 if token.contents in parse_until: 224 # put token back on token list so calling code knows why it terminated 234 # put token back on token list so calling 235 # code knows why it terminated 225 236 self.prepend_token(token) 226 237 return nodelist 227 238 try: 228 239 command = token.contents.split()[0] 229 240 except IndexError: 230 241 self.empty_block_tag(token) 231 # execute callback function for this tag and append resulting node 242 # execute callback function for this tag and append 243 # resulting node 232 244 self.enter_command(command, token) 233 245 try: 234 246 compile_func = self.tags[command] … … class Parser(object): 264 276 if nodelist.contains_nontext: 265 277 raise AttributeError 266 278 except AttributeError: 267 raise TemplateSyntaxError("%r must be the first tag in the template." % node) 279 raise TemplateSyntaxError("%r must be the first tag " 280 "in the template." % node) 268 281 if isinstance(nodelist, NodeList) and not isinstance(node, TextNode): 269 282 nodelist.contains_nontext = True 270 283 nodelist.append(node) … … class Parser(object): 286 299 287 300 def invalid_block_tag(self, token, command, parse_until=None): 288 301 if parse_until: 289 raise self.error(token, "Invalid block tag: '%s', expected %s" % (command, get_text_list(["'%s'" % p for p in parse_until]))) 302 raise self.error(token, "Invalid block tag: '%s', expected %s" % 303 (command, get_text_list(["'%s'" % p for p in parse_until]))) 290 304 raise self.error(token, "Invalid block tag: '%s'" % command) 291 305 292 306 def unclosed_block_tag(self, parse_until): 293 raise self.error(None, "Unclosed tags: %s " % 307 raise self.error(None, "Unclosed tags: %s " % ', '.join(parse_until)) 294 308 295 309 def compile_function_error(self, token, e): 296 310 pass … … class Parser(object): 320 334 321 335 class TokenParser(object): 322 336 """ 323 Subclass this and implement the top() method to parse a template line. When 324 instantiating the parser, pass in the line from the Django template parser. 337 Subclass this and implement the top() method to parse a template line. 338 When instantiating the parser, pass in the line from the Django template 339 parser. 325 340 326 341 The parser's "tagname" instance-variable stores the name of the tag that 327 342 the filter was called with. … … class TokenParser(object): 351 366 subject = self.subject 352 367 i = self.pointer 353 368 if i >= len(subject): 354 raise TemplateSyntaxError("expected another tag, found end of string: %s" % subject) 369 raise TemplateSyntaxError("expected another tag, found " 370 "end of string: %s" % subject) 355 371 p = i 356 372 while i < len(subject) and subject[i] not in (' ', '\t'): 357 373 i += 1 … … class TokenParser(object): 363 379 return s 364 380 365 381 def value(self): 366 "A microparser that parses for a value: some string constant or variable name." 382 """ 383 A microparser that parses for a value: some string constant or 384 variable name. 385 """ 367 386 subject = self.subject 368 387 i = self.pointer 369 388 370 389 def next_space_index(subject, i): 371 "Increment pointer until a real space (i.e. a space not within quotes) is encountered" 390 """ 391 Increment pointer until a real space (i.e. a space not within 392 quotes) is encountered 393 """ 372 394 while i < len(subject) and subject[i] not in (' ', '\t'): 373 395 if subject[i] in ('"', "'"): 374 396 c = subject[i] … … class TokenParser(object): 376 398 while i < len(subject) and subject[i] != c: 377 399 i += 1 378 400 if i >= len(subject): 379 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject)) 401 raise TemplateSyntaxError("Searching for value. " 402 "Unexpected end of string in column %d: %s" % 403 (i, subject)) 380 404 i += 1 381 405 return i 382 406 383 407 if i >= len(subject): 384 raise TemplateSyntaxError("Searching for value. Expected another value but found end of string: %s" % subject) 408 raise TemplateSyntaxError("Searching for value. Expected another " 409 "value but found end of string: %s" % 410 subject) 385 411 if subject[i] in ('"', "'"): 386 412 p = i 387 413 i += 1 388 414 while i < len(subject) and subject[i] != subject[p]: 389 415 i += 1 390 416 if i >= len(subject): 391 raise TemplateSyntaxError("Searching for value. Unexpected end of string in column %d: %s" % (i, subject)) 417 raise TemplateSyntaxError("Searching for value. Unexpected " 418 "end of string in column %d: %s" % 419 (i, subject)) 392 420 i += 1 393 421 394 # Continue parsing until next "real" space, so that filters are also included 422 # Continue parsing until next "real" space, 423 # so that filters are also included 395 424 i = next_space_index(subject, i) 396 425 397 426 res = subject[p:i] … … constant_string = r""" 419 448 %(strdq)s| 420 449 %(strsq)s) 421 450 """ % { 422 'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string423 'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string424 'i18n_open' 425 'i18n_close' 451 'strdq': r'"[^"\\]*(?:\\.[^"\\]*)*"', # double-quoted string 452 'strsq': r"'[^'\\]*(?:\\.[^'\\]*)*'", # single-quoted string 453 'i18n_open': re.escape("_("), 454 'i18n_close': re.escape(")"), 426 455 } 427 456 constant_string = constant_string.replace("\n", "") 428 457 … … filter_raw_string = r""" 440 469 )""" % { 441 470 'constant': constant_string, 442 471 'num': r'[-+\.]?\d[\d\.e]*', 443 'var_chars': "\w\." 472 'var_chars': "\w\.", 444 473 'filter_sep': re.escape(FILTER_SEPARATOR), 445 474 'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR), 446 475 } 447 476 448 filter_re = re.compile(filter_raw_string, re.UNICODE |re.VERBOSE)477 filter_re = re.compile(filter_raw_string, re.UNICODE | re.VERBOSE) 449 478 450 479 class FilterExpression(object): 451 r"""480 """ 452 481 Parses a variable token and its optional filters (all as a single string), 453 482 and return a list of tuples of the filter name and arguments. 454 Sample: 483 Sample:: 484 455 485 >>> token = 'variable|default:"Default value"|date:"Y-m-d"' 456 486 >>> p = Parser('') 457 487 >>> fe = FilterExpression(token, p) … … class FilterExpression(object): 472 502 for match in matches: 473 503 start = match.start() 474 504 if upto != start: 475 raise TemplateSyntaxError("Could not parse some characters: %s|%s|%s" % \ 476 (token[:upto], token[upto:start], token[start:])) 505 raise TemplateSyntaxError("Could not parse some characters: " 506 "%s|%s|%s" % 507 (token[:upto], token[upto:start], 508 token[start:])) 477 509 if var_obj is None: 478 510 var, constant = match.group("var", "constant") 479 511 if constant: … … class FilterExpression(object): 482 514 except VariableDoesNotExist: 483 515 var_obj = None 484 516 elif var is None: 485 raise TemplateSyntaxError("Could not find variable at start of %s." % token) 517 raise TemplateSyntaxError("Could not find variable at " 518 "start of %s." % token) 486 519 else: 487 520 var_obj = Variable(var) 488 521 else: … … class FilterExpression(object): 498 531 filters.append((filter_func, args)) 499 532 upto = match.end() 500 533 if upto != len(token): 501 raise TemplateSyntaxError("Could not parse the remainder: '%s' from '%s'" % (token[upto:], token)) 534 raise TemplateSyntaxError("Could not parse the remainder: '%s' " 535 "from '%s'" % (token[upto:], token)) 502 536 503 537 self.filters = filters 504 538 self.var = var_obj … … class FilterExpression(object): 559 593 provided.pop(0) 560 594 except IndexError: 561 595 # Not enough 562 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen)) 596 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % 597 (name, len(nondefs), plen)) 563 598 564 599 # Defaults can be overridden. 565 600 defaults = defaults and list(defaults) or [] … … class FilterExpression(object): 568 603 defaults.pop(0) 569 604 except IndexError: 570 605 # Too many. 571 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % (name, len(nondefs), plen)) 606 raise TemplateSyntaxError("%s requires %d arguments, %d provided" % 607 (name, len(nondefs), plen)) 572 608 573 609 return True 574 610 args_check = staticmethod(args_check) … … def resolve_variable(path, context): 586 622 return Variable(path).resolve(context) 587 623 588 624 class Variable(object): 589 r"""590 A template variable, resolvable against a given context. The variable may be591 a hard-coded string (if it begins and ends with single or double quote625 """ 626 A template variable, resolvable against a given context. The variable may 627 be a hard-coded string (if it begins and ends with single or double quote 592 628 marks):: 593 629 594 630 >>> c = {'article': {'section':u'News'}} … … class Variable(object): 642 678 # Otherwise we'll set self.lookups so that resolve() knows we're 643 679 # dealing with a bonafide variable 644 680 if var.find(VARIABLE_ATTRIBUTE_SEPARATOR + '_') > -1 or var[0] == '_': 645 raise TemplateSyntaxError("Variables and attributes may not begin with underscores: '%s'" % var) 681 raise TemplateSyntaxError("Variables and attributes may " 682 "not begin with underscores: '%s'" % 683 var) 646 684 self.lookups = tuple(var.split(VARIABLE_ATTRIBUTE_SEPARATOR)) 647 685 648 686 def resolve(self, context): … … class Variable(object): 673 711 instead. 674 712 """ 675 713 current = context 676 try: # catch-all for silent variable failures714 try: # catch-all for silent variable failures 677 715 for bit in self.lookups: 678 try: # dictionary lookup716 try: # dictionary lookup 679 717 current = current[bit] 680 718 except (TypeError, AttributeError, KeyError): 681 try: # attribute lookup719 try: # attribute lookup 682 720 current = getattr(current, bit) 683 721 except (TypeError, AttributeError): 684 try: # list-index lookup722 try: # list-index lookup 685 723 current = current[int(bit)] 686 except (IndexError, # list index out of range 687 ValueError, # invalid literal for int() 688 KeyError, # current is a dict without `int(bit)` key 689 TypeError, # unsubscriptable object 690 ): 691 raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute 724 except (IndexError, # list index out of range 725 ValueError, # invalid literal for int() 726 KeyError, # current is a dict without `int(bit)` key 727 TypeError): # unsubscriptable object 728 raise VariableDoesNotExist("Failed lookup for key " 729 "[%s] in %r", 730 (bit, current)) # missing attribute 692 731 if callable(current): 693 732 if getattr(current, 'do_not_call_in_templates', False): 694 733 pass … … class Variable(object): 700 739 except TypeError: # arguments *were* required 701 740 # GOTCHA: This will also catch any TypeError 702 741 # raised in the function itself. 703 current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call742 current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call 704 743 except Exception, e: 705 744 if getattr(e, 'silent_variable_failure', False): 706 745 current = settings.TEMPLATE_STRING_IF_INVALID … … class Node(object): 723 762 yield self 724 763 725 764 def get_nodes_by_type(self, nodetype): 726 "Return a list of all nodes (within this node and its nodelist) of the given type" 765 """ 766 Return a list of all nodes (within this node and its nodelist) 767 of the given type 768 """ 727 769 nodes = [] 728 770 if isinstance(self, nodetype): 729 771 nodes.append(self) … … def _render_value_in_context(value, context): 776 818 """ 777 819 value = localize(value, use_l10n=context.use_l10n) 778 820 value = force_unicode(value) 779 if (context.autoescape and not isinstance(value, SafeData)) or isinstance(value, EscapeData): 821 if ((context.autoescape and not isinstance(value, SafeData)) or 822 isinstance(value, EscapeData)): 780 823 return escape(value) 781 824 else: 782 825 return value … … class VariableNode(Node): 793 836 output = self.filter_expression.resolve(context) 794 837 except UnicodeDecodeError: 795 838 # Unicode conversion can fail sometimes for reasons out of our 796 # control (e.g. exception rendering). In that case, we fail quietly. 839 # control (e.g. exception rendering). In that case, we fail 840 # quietly. 797 841 return '' 798 842 return _render_value_in_context(output, context) 799 843 800 def generic_tag_compiler(params, defaults, name, node_class, parser, token): 801 "Returns a template.Node subclass." 802 bits = token.split_contents()[1:] 803 bmax = len(params) 804 def_len = defaults and len(defaults) or 0 805 bmin = bmax - def_len 806 if(len(bits) < bmin or len(bits) > bmax): 807 if bmin == bmax: 808 message = "%s takes %s arguments" % (name, bmin) 844 # Regex for token keyword arguments 845 kwarg_re = re.compile(r"(?:(\w+)=)?(.+)") 846 847 def token_kwargs(bits, parser, support_legacy=False): 848 """ 849 A utility method for parsing token keyword arguments. 850 851 :param bits: A list containing remainder of the token (split by spaces) 852 that is to be checked for arguments. Valid arguments will be removed 853 from this list. 854 855 :param support_legacy: If set to true ``True``, the legacy format 856 ``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1`` 857 format is allowed. 858 859 :returns: A dictionary of the arguments retrieved from the ``bits`` token 860 list. 861 862 There is no requirement for all remaining token ``bits`` to be keyword 863 arguments, so the dictionary will be returned as soon as an invalid 864 argument format is reached. 865 """ 866 if not bits: 867 return {} 868 match = kwarg_re.match(bits[0]) 869 kwarg_format = match and match.group(1) 870 if not kwarg_format: 871 if not support_legacy: 872 return {} 873 if len(bits) < 3 or bits[1] != 'as': 874 return {} 875 876 kwargs = {} 877 while bits: 878 if kwarg_format: 879 match = kwarg_re.match(bits[0]) 880 if not match or not match.group(1): 881 return kwargs 882 key, value = match.groups() 883 del bits[:1] 809 884 else: 810 message = "%s takes between %s and %s arguments" % (name, bmin, bmax) 811 raise TemplateSyntaxError(message) 812 return node_class(bits) 885 if len(bits) < 3 or bits[1] != 'as': 886 return kwargs 887 key, value = bits[2], bits[0] 888 del bits[:3] 889 kwargs[key] = parser.compile_filter(value) 890 if bits and not kwarg_format: 891 if bits[0] != 'and': 892 return kwargs 893 del bits[:1] 894 return kwargs 895 896 def parse_bits(parser, bits, params, varargs, varkw, defaults, 897 takes_context, name): 898 """ 899 Parses bits for template tag helpers (simple_tag, include_tag and 900 assignment_tag), in particular by detecting syntax errors and by 901 extracting positional and keyword arguments. 902 """ 903 if takes_context: 904 if params[0] == 'context': 905 params = params[1:] 906 else: 907 raise TemplateSyntaxError( 908 "'%s' is decorated with takes_context=True so it must " 909 "have a first argument of 'context'" % name) 910 args = [] 911 kwargs = {} 912 unhandled_params = list(params) 913 for bit in bits: 914 # First we try to extract a potential kwarg from the bit 915 kwarg = token_kwargs([bit], parser) 916 if kwarg: 917 # The kwarg was successfully extracted 918 param, value = kwarg.items()[0] 919 if param not in params and varkw is None: 920 # An unexpected keyword argument was supplied 921 raise TemplateSyntaxError( 922 "'%s' received unexpected keyword argument '%s'" % 923 (name, param)) 924 elif param in kwargs: 925 # The keyword argument has already been supplied once 926 raise TemplateSyntaxError( 927 "'%s' received multiple values for keyword argument '%s'" % 928 (name, param)) 929 else: 930 # All good, record the keyword argument 931 kwargs[str(param)] = value 932 if param in unhandled_params: 933 # If using the keyword syntax for a positional arg, then 934 # consume it. 935 unhandled_params.remove(param) 936 else: 937 if kwargs: 938 raise TemplateSyntaxError( 939 "'%s' received some positional argument(s) after some " 940 "keyword argument(s)" % name) 941 else: 942 # Record the positional argument 943 args.append(parser.compile_filter(bit)) 944 try: 945 # Consume from the list of expected positional arguments 946 unhandled_params.pop(0) 947 except IndexError: 948 if varargs is None: 949 raise TemplateSyntaxError( 950 "'%s' received too many positional arguments" % 951 name) 952 if defaults is not None: 953 # Consider the last n params handled, where n is the 954 # number of defaults. 955 unhandled_params = unhandled_params[:-len(defaults)] 956 if unhandled_params: 957 # Some positional arguments were not supplied 958 raise TemplateSyntaxError( 959 u"'%s' did not receive value(s) for the argument(s): %s" % 960 (name, u", ".join([u"'%s'" % p for p in unhandled_params]))) 961 return args, kwargs 962 963 def generic_tag_compiler(parser, token, params, varargs, varkw, defaults, 964 name, takes_context, node_class): 965 """Returns a template.Node subclass.""" 966 bits = token.split_contents()[1:] 967 args, kwargs = parse_bits(parser, bits, params, varargs, varkw, 968 defaults, takes_context, name) 969 return node_class(takes_context, args, kwargs) 970 971 class TagHelperNode(Node): 972 """ 973 Base class for tag helper nodes such as SimpleNode, InclusionNode and 974 AssignmentNode. Manages the positional and keyword arguments to be passed 975 to the decorated function. 976 """ 977 978 def __init__(self, takes_context, args, kwargs): 979 self.takes_context = takes_context 980 self.args = args 981 self.kwargs = kwargs 982 983 def get_resolved_arguments(self, context): 984 resolved_args = [var.resolve(context) for var in self.args] 985 if self.takes_context: 986 resolved_args = [context] + resolved_args 987 resolved_kwargs = dict((k, v.resolve(context)) 988 for k, v in self.kwargs.items()) 989 return resolved_args, resolved_kwargs 813 990 814 991 class Library(object): 815 992 def __init__(self): … … class Library(object): 817 994 self.tags = {} 818 995 819 996 def tag(self, name=None, compile_function=None): 820 if name == None and compile_function ==None:997 if name is None and compile_function is None: 821 998 # @register.tag() 822 999 return self.tag_function 823 elif name != None and compile_function ==None:1000 elif name is not None and compile_function is None: 824 1001 if callable(name): 825 1002 # @register.tag 826 1003 return self.tag_function(name) … … class Library(object): 829 1006 def dec(func): 830 1007 return self.tag(name, func) 831 1008 return dec 832 elif name != None and compile_function !=None:1009 elif name is not None and compile_function is not None: 833 1010 # register.tag('somename', somefunc) 834 1011 self.tags[name] = compile_function 835 1012 return compile_function 836 1013 else: 837 raise InvalidTemplateLibrary("Unsupported arguments to Library.tag: (%r, %r)", (name, compile_function)) 1014 raise InvalidTemplateLibrary("Unsupported arguments to " 1015 "Library.tag: (%r, %r)", (name, compile_function)) 838 1016 839 def tag_function(self, func):1017 def tag_function(self, func): 840 1018 self.tags[getattr(func, "_decorated_function", func).__name__] = func 841 1019 return func 842 1020 843 1021 def filter(self, name=None, filter_func=None): 844 if name == None and filter_func ==None:1022 if name is None and filter_func is None: 845 1023 # @register.filter() 846 1024 return self.filter_function 847 elif filter_func ==None:1025 elif filter_func is None: 848 1026 if callable(name): 849 1027 # @register.filter 850 1028 return self.filter_function(name) … … class Library(object): 853 1031 def dec(func): 854 1032 return self.filter(name, func) 855 1033 return dec 856 elif name != None and filter_func !=None:1034 elif name is not None and filter_func is not None: 857 1035 # register.filter('somename', somefunc) 858 1036 self.filters[name] = filter_func 859 1037 return filter_func 860 1038 else: 861 raise InvalidTemplateLibrary("Unsupported arguments to Library.filter: (%r, %r)", (name, filter_func)) 1039 raise InvalidTemplateLibrary("Unsupported arguments to " 1040 "Library.filter: (%r, %r)", (name, filter_func)) 862 1041 863 1042 def filter_function(self, func): 864 1043 self.filters[getattr(func, "_decorated_function", func).__name__] = func … … class Library(object): 866 1045 867 1046 def simple_tag(self, func=None, takes_context=None, name=None): 868 1047 def dec(func): 869 params, xx, xxx, defaults = getargspec(func) 870 if takes_context: 871 if params[0] == 'context': 872 params = params[1:] 873 else: 874 raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'") 1048 params, varargs, varkw, defaults = getargspec(func) 875 1049 876 class SimpleNode(Node): 877 def __init__(self, vars_to_resolve): 878 self.vars_to_resolve = map(Variable, vars_to_resolve) 1050 class SimpleNode(TagHelperNode): 879 1051 880 1052 def render(self, context): 881 resolved_ vars = [var.resolve(context) for var in self.vars_to_resolve]882 if takes_context:883 func_args = [context] + resolved_vars 884 else:885 func_args = resolved_vars886 return func(*func_args)887 888 function_name = name or getattr(func, '_decorated_function', func).__name__889 compile_func = partial(generic_tag_compiler, params, defaults, function_name,SimpleNode)1053 resolved_args, resolved_kwargs = self.get_resolved_arguments(context) 1054 return func(*resolved_args, **resolved_kwargs) 1055 1056 function_name = (name or 1057 getattr(func, '_decorated_function', func).__name__) 1058 compile_func = partial(generic_tag_compiler, 1059 params=params, varargs=varargs, varkw=varkw, 1060 defaults=defaults, name=function_name, 1061 takes_context=takes_context, node_class=SimpleNode) 890 1062 compile_func.__doc__ = func.__doc__ 891 1063 self.tag(function_name, compile_func) 892 1064 return func … … class Library(object): 902 1074 903 1075 def assignment_tag(self, func=None, takes_context=None, name=None): 904 1076 def dec(func): 905 params, xx, xxx, defaults = getargspec(func) 906 if takes_context: 907 if params[0] == 'context': 908 params = params[1:] 909 else: 910 raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'") 1077 params, varargs, varkw, defaults = getargspec(func) 911 1078 912 class AssignmentNode( Node):913 def __init__(self, params_vars, target_var):914 s elf.params_vars = map(Variable, params_vars)1079 class AssignmentNode(TagHelperNode): 1080 def __init__(self, takes_context, args, kwargs, target_var): 1081 super(AssignmentNode, self).__init__(takes_context, args, kwargs) 915 1082 self.target_var = target_var 916 1083 917 1084 def render(self, context): 918 resolved_vars = [var.resolve(context) for var in self.params_vars] 919 if takes_context: 920 func_args = [context] + resolved_vars 921 else: 922 func_args = resolved_vars 923 context[self.target_var] = func(*func_args) 1085 resolved_args, resolved_kwargs = self.get_resolved_arguments(context) 1086 context[self.target_var] = func(*resolved_args, **resolved_kwargs) 924 1087 return '' 925 1088 1089 function_name = (name or 1090 getattr(func, '_decorated_function', func).__name__) 1091 926 1092 def compile_func(parser, token): 927 bits = token.split_contents() 928 tag_name = bits[0] 929 bits = bits[1:] 930 params_max = len(params) 931 defaults_length = defaults and len(defaults) or 0 932 params_min = params_max - defaults_length 933 if (len(bits) < 2 or bits[-2] != 'as'): 1093 bits = token.split_contents()[1:] 1094 if len(bits) < 2 or bits[-2] != 'as': 934 1095 raise TemplateSyntaxError( 935 1096 "'%s' tag takes at least 2 arguments and the " 936 "second last argument must be 'as'" % tag_name) 937 params_vars = bits[:-2] 1097 "second last argument must be 'as'" % function_name) 938 1098 target_var = bits[-1] 939 if (len(params_vars) < params_min or 940 len(params_vars) > params_max): 941 if params_min == params_max: 942 raise TemplateSyntaxError( 943 "%s takes %s arguments" % (tag_name, params_min)) 944 else: 945 raise TemplateSyntaxError( 946 "%s takes between %s and %s arguments" 947 % (tag_name, params_min, params_max)) 948 return AssignmentNode(params_vars, target_var) 1099 bits = bits[:-2] 1100 args, kwargs = parse_bits(parser, bits, params, 1101 varargs, varkw, defaults, takes_context, function_name) 1102 return AssignmentNode(takes_context, args, kwargs, target_var) 949 1103 950 function_name = name or getattr(func, '_decorated_function', func).__name__951 1104 compile_func.__doc__ = func.__doc__ 952 1105 self.tag(function_name, compile_func) 953 1106 return func … … class Library(object): 963 1116 964 1117 def inclusion_tag(self, file_name, context_class=Context, takes_context=False, name=None): 965 1118 def dec(func): 966 params, xx, xxx, defaults = getargspec(func) 967 if takes_context: 968 if params[0] == 'context': 969 params = params[1:] 970 else: 971 raise TemplateSyntaxError("Any tag function decorated with takes_context=True must have a first argument of 'context'") 1119 params, varargs, varkw, defaults = getargspec(func) 972 1120 973 class InclusionNode(Node): 974 def __init__(self, vars_to_resolve): 975 self.vars_to_resolve = map(Variable, vars_to_resolve) 1121 class InclusionNode(TagHelperNode): 976 1122 977 1123 def render(self, context): 978 resolved_vars = [var.resolve(context) for var in self.vars_to_resolve] 979 if takes_context: 980 args = [context] + resolved_vars 981 else: 982 args = resolved_vars 983 984 dict = func(*args) 1124 resolved_args, resolved_kwargs = self.get_resolved_arguments(context) 1125 _dict = func(*resolved_args, **resolved_kwargs) 985 1126 986 1127 if not getattr(self, 'nodelist', False): 987 1128 from django.template.loader import get_template, select_template … … class Library(object): 992 1133 else: 993 1134 t = get_template(file_name) 994 1135 self.nodelist = t.nodelist 995 new_context = context_class( dict, **{1136 new_context = context_class(_dict, **{ 996 1137 'autoescape': context.autoescape, 997 1138 'current_app': context.current_app, 998 1139 'use_l10n': context.use_l10n, 999 1140 }) 1000 # Copy across the CSRF token, if present, because inclusion 1001 # tags are often used for forms, and we need instructions 1002 # for using CSRF protection to be as simple as possible. 1141 # Copy across the CSRF token, if present, because 1142 # inclusion tags are often used for forms, and we need 1143 # instructions for using CSRF protection to be as simple 1144 # as possible. 1003 1145 csrf_token = context.get('csrf_token', None) 1004 1146 if csrf_token is not None: 1005 1147 new_context['csrf_token'] = csrf_token 1006 1148 return self.nodelist.render(new_context) 1007 1149 1008 function_name = name or getattr(func, '_decorated_function', func).__name__ 1009 compile_func = partial(generic_tag_compiler, params, defaults, function_name, InclusionNode) 1150 function_name = (name or 1151 getattr(func, '_decorated_function', func).__name__) 1152 compile_func = partial(generic_tag_compiler, 1153 params=params, varargs=varargs, varkw=varkw, 1154 defaults=defaults, name=function_name, 1155 takes_context=takes_context, node_class=InclusionNode) 1010 1156 compile_func.__doc__ = func.__doc__ 1011 1157 self.tag(function_name, compile_func) 1012 1158 return func … … def import_library(taglib_module): 1018 1164 Verifies that the library contains a 'register' attribute, and 1019 1165 returns that attribute as the representation of the library 1020 1166 """ 1021 app_path, taglib = taglib_module.rsplit('.', 1)1167 app_path, taglib = taglib_module.rsplit('.', 1) 1022 1168 app_module = import_module(app_path) 1023 1169 try: 1024 1170 mod = import_module(taglib_module) 1025 1171 except ImportError, e: 1026 # If the ImportError is because the taglib submodule does not exist, that's not 1027 # an error that should be raised. If the submodule exists and raised an ImportError 1028 # on the attempt to load it, that we want to raise. 1172 # If the ImportError is because the taglib submodule does not exist, 1173 # that's not an error that should be raised. If the submodule exists 1174 # and raised an ImportError on the attempt to load it, that we want 1175 # to raise. 1029 1176 if not module_has_submodule(app_module, taglib): 1030 1177 return None 1031 1178 else: 1032 raise InvalidTemplateLibrary("ImportError raised loading %s: %s" % (taglib_module, e)) 1179 raise InvalidTemplateLibrary("ImportError raised loading %s: %s" % 1180 (taglib_module, e)) 1033 1181 try: 1034 1182 return mod.register 1035 1183 except AttributeError: 1036 raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % taglib_module) 1184 raise InvalidTemplateLibrary("Template library %s does not have " 1185 "a variable named 'register'" % 1186 taglib_module) 1037 1187 1038 1188 templatetags_modules = [] 1039 1189 … … def get_templatetags_modules(): 1045 1195 global templatetags_modules 1046 1196 if not templatetags_modules: 1047 1197 _templatetags_modules = [] 1048 # Populate list once per process. Mutate the local list first, and then1049 # assign it to the global name to ensure there are no cases where two1050 # t hreads try to populate it simultaneously.1198 # Populate list once per process. Mutate the local list first, and 1199 # then assign it to the global name to ensure there are no cases where 1200 # two threads try to populate it simultaneously. 1051 1201 for app_module in ['django'] + list(settings.INSTALLED_APPS): 1052 1202 try: 1053 1203 templatetag_module = '%s.templatetags' % app_module … … def get_library(library_name): 1062 1212 """ 1063 1213 Load the template library module with the given name. 1064 1214 1065 If library is not already loaded loop over all templatetags modules to locate it. 1215 If library is not already loaded loop over all templatetags modules 1216 to locate it. 1066 1217 1067 1218 {% load somelib %} and {% load someotherlib %} loops twice. 1068 1219 1069 Subsequent loads eg. {% load somelib %} in the same process will grab the cached1070 module from libraries.1220 Subsequent loads eg. {% load somelib %} in the same process will grab 1221 the cached module from libraries. 1071 1222 """ 1072 1223 lib = libraries.get(library_name, None) 1073 1224 if not lib: … … def get_library(library_name): 1081 1232 libraries[library_name] = lib 1082 1233 break 1083 1234 if not lib: 1084 raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules))) 1235 raise InvalidTemplateLibrary("Template library %s not found, " 1236 "tried %s" % 1237 (library_name, 1238 ','.join(tried_modules))) 1085 1239 return lib 1086 1240 1241 1087 1242 def add_to_builtins(module): 1088 1243 builtins.append(import_library(module)) 1089 1244 1245 1090 1246 add_to_builtins('django.template.defaulttags') 1091 1247 add_to_builtins('django.template.defaultfilters') -
django/template/defaulttags.py
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 0b039a5..9620d1c 100644
a b from django.template.base import (Node, NodeList, Template, Library, 10 10 TemplateSyntaxError, VariableDoesNotExist, InvalidTemplateLibrary, 11 11 BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, 12 12 SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END, 13 get_library )13 get_library, token_kwargs, kwarg_re) 14 14 from django.template.smartif import IfParser, Literal 15 15 from django.template.defaultfilters import date 16 16 from django.utils.encoding import smart_str, smart_unicode 17 17 from django.utils.safestring import mark_safe 18 18 19 19 register = Library() 20 # Regex for token keyword arguments21 kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")22 23 def token_kwargs(bits, parser, support_legacy=False):24 """25 A utility method for parsing token keyword arguments.26 27 :param bits: A list containing remainder of the token (split by spaces)28 that is to be checked for arguments. Valid arguments will be removed29 from this list.30 31 :param support_legacy: If set to true ``True``, the legacy format32 ``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``33 format is allowed.34 35 :returns: A dictionary of the arguments retrieved from the ``bits`` token36 list.37 38 There is no requirement for all remaining token ``bits`` to be keyword39 arguments, so the dictionary will be returned as soon as an invalid40 argument format is reached.41 """42 if not bits:43 return {}44 match = kwarg_re.match(bits[0])45 kwarg_format = match and match.group(1)46 if not kwarg_format:47 if not support_legacy:48 return {}49 if len(bits) < 3 or bits[1] != 'as':50 return {}51 52 kwargs = {}53 while bits:54 if kwarg_format:55 match = kwarg_re.match(bits[0])56 if not match or not match.group(1):57 return kwargs58 key, value = match.groups()59 del bits[:1]60 else:61 if len(bits) < 3 or bits[1] != 'as':62 return kwargs63 key, value = bits[2], bits[0]64 del bits[:3]65 kwargs[key] = parser.compile_filter(value)66 if bits and not kwarg_format:67 if bits[0] != 'and':68 return kwargs69 del bits[:1]70 return kwargs71 20 72 21 class AutoEscapeControlNode(Node): 73 22 """Implements the actions of the autoescape tag.""" -
docs/releases/1.4.txt
diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt index 9cf268a..ad241d9 100644
a b Django 1.4 also includes several smaller improvements worth noting: 291 291 292 292 * Customizable names for :meth:`~django.template.Library.simple_tag`. 293 293 294 * Added ``*args`` and ``*kwargs`` support to 295 :meth:`~django.template.Library.simple_tag`, 296 :meth:`~django.template.Library.inclusion_tag` and the newly introduced 297 :meth:`~django.template.Library.assignment_tag`. 298 294 299 * In the documentation, a helpful :doc:`security overview </topics/security>` 295 300 page. 296 301 -
tests/regressiontests/templates/custom.py
diff --git a/tests/regressiontests/templates/custom.py b/tests/regressiontests/templates/custom.py index d781874..c05229e 100644
a b class CustomTagTests(TestCase): 35 35 t = template.Template('{% load custom %}{% params_and_context 37 %}') 36 36 self.assertEqual(t.render(c), u'params_and_context - Expected result (context value: 42): 37') 37 37 38 t = template.Template('{% load custom %}{% simple_two_params 37 42 %}') 39 self.assertEqual(t.render(c), u'simple_two_params - Expected result: 37, 42') 40 41 t = template.Template('{% load custom %}{% simple_one_default 37 %}') 42 self.assertEqual(t.render(c), u'simple_one_default - Expected result: 37, hi') 43 44 t = template.Template('{% load custom %}{% simple_one_default 37 two="hello" %}') 45 self.assertEqual(t.render(c), u'simple_one_default - Expected result: 37, hello') 46 47 t = template.Template('{% load custom %}{% simple_one_default one=99 two="hello" %}') 48 self.assertEqual(t.render(c), u'simple_one_default - Expected result: 99, hello') 49 50 self.assertRaisesRegexp(template.TemplateSyntaxError, 51 "'simple_one_default' received unexpected keyword argument 'three'", 52 template.Template, '{% load custom %}{% simple_one_default 99 two="hello" three="foo" %}') 53 54 t = template.Template('{% load custom %}{% simple_one_default 37 42 %}') 55 self.assertEqual(t.render(c), u'simple_one_default - Expected result: 37, 42') 56 57 t = template.Template('{% load custom %}{% simple_unlimited_args 37 %}') 58 self.assertEqual(t.render(c), u'simple_unlimited_args - Expected result: 37, hi') 59 60 t = template.Template('{% load custom %}{% simple_unlimited_args 37 42 56 89 %}') 61 self.assertEqual(t.render(c), u'simple_unlimited_args - Expected result: 37, 42, 56, 89') 62 63 t = template.Template('{% load custom %}{% simple_only_unlimited_args %}') 64 self.assertEqual(t.render(c), u'simple_only_unlimited_args - Expected result: ') 65 66 t = template.Template('{% load custom %}{% simple_only_unlimited_args 37 42 56 89 %}') 67 self.assertEqual(t.render(c), u'simple_only_unlimited_args - Expected result: 37, 42, 56, 89') 68 69 self.assertRaisesRegexp(template.TemplateSyntaxError, 70 "'simple_two_params' received too many positional arguments", 71 template.Template, '{% load custom %}{% simple_two_params 37 42 56 %}') 72 73 self.assertRaisesRegexp(template.TemplateSyntaxError, 74 "'simple_one_default' received too many positional arguments", 75 template.Template, '{% load custom %}{% simple_one_default 37 42 56 %}') 76 77 t = template.Template('{% load custom %}{% simple_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 %}') 78 self.assertEqual(t.render(c), u'simple_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4') 79 80 self.assertRaisesRegexp(template.TemplateSyntaxError, 81 "'simple_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)", 82 template.Template, '{% load custom %}{% simple_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 %}') 83 84 self.assertRaisesRegexp(template.TemplateSyntaxError, 85 "'simple_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'", 86 template.Template, '{% load custom %}{% simple_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" %}') 87 38 88 def test_simple_tag_registration(self): 39 89 # Test that the decorators preserve the decorated function's docstring, name and attributes. 40 90 self.verify_tag(custom.no_params, 'no_params') … … class CustomTagTests(TestCase): 42 92 self.verify_tag(custom.explicit_no_context, 'explicit_no_context') 43 93 self.verify_tag(custom.no_params_with_context, 'no_params_with_context') 44 94 self.verify_tag(custom.params_and_context, 'params_and_context') 95 self.verify_tag(custom.simple_unlimited_args_kwargs, 'simple_unlimited_args_kwargs') 96 self.verify_tag(custom.simple_tag_without_context_parameter, 'simple_tag_without_context_parameter') 45 97 46 98 def test_simple_tag_missing_context(self): 47 # That the 'context' parameter must be present when takes_context is True 48 def a_simple_tag_without_parameters(arg): 49 """Expected __doc__""" 50 return "Expected result" 51 52 register = template.Library() 53 decorator = register.simple_tag(takes_context=True) 54 self.assertRaises(template.TemplateSyntaxError, decorator, a_simple_tag_without_parameters) 99 # The 'context' parameter must be present when takes_context is True 100 self.assertRaisesRegexp(template.TemplateSyntaxError, 101 "'simple_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'", 102 template.Template, '{% load custom %}{% simple_tag_without_context_parameter 123 %}') 55 103 56 104 def test_inclusion_tags(self): 57 105 c = template.Context({'value': 42}) … … class CustomTagTests(TestCase): 71 119 t = template.Template('{% load custom %}{% inclusion_params_and_context 37 %}') 72 120 self.assertEqual(t.render(c), u'inclusion_params_and_context - Expected result (context value: 42): 37\n') 73 121 122 t = template.Template('{% load custom %}{% inclusion_two_params 37 42 %}') 123 self.assertEqual(t.render(c), u'inclusion_two_params - Expected result: 37, 42\n') 124 125 t = template.Template('{% load custom %}{% inclusion_one_default 37 %}') 126 self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 37, hi\n') 127 128 t = template.Template('{% load custom %}{% inclusion_one_default 37 two="hello" %}') 129 self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 37, hello\n') 130 131 t = template.Template('{% load custom %}{% inclusion_one_default one=99 two="hello" %}') 132 self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 99, hello\n') 133 134 self.assertRaisesRegexp(template.TemplateSyntaxError, 135 "'inclusion_one_default' received unexpected keyword argument 'three'", 136 template.Template, '{% load custom %}{% inclusion_one_default 99 two="hello" three="foo" %}') 137 138 t = template.Template('{% load custom %}{% inclusion_one_default 37 42 %}') 139 self.assertEqual(t.render(c), u'inclusion_one_default - Expected result: 37, 42\n') 140 141 t = template.Template('{% load custom %}{% inclusion_unlimited_args 37 %}') 142 self.assertEqual(t.render(c), u'inclusion_unlimited_args - Expected result: 37, hi\n') 143 144 t = template.Template('{% load custom %}{% inclusion_unlimited_args 37 42 56 89 %}') 145 self.assertEqual(t.render(c), u'inclusion_unlimited_args - Expected result: 37, 42, 56, 89\n') 146 147 t = template.Template('{% load custom %}{% inclusion_only_unlimited_args %}') 148 self.assertEqual(t.render(c), u'inclusion_only_unlimited_args - Expected result: \n') 149 150 t = template.Template('{% load custom %}{% inclusion_only_unlimited_args 37 42 56 89 %}') 151 self.assertEqual(t.render(c), u'inclusion_only_unlimited_args - Expected result: 37, 42, 56, 89\n') 152 153 self.assertRaisesRegexp(template.TemplateSyntaxError, 154 "'inclusion_two_params' received too many positional arguments", 155 template.Template, '{% load custom %}{% inclusion_two_params 37 42 56 %}') 156 157 self.assertRaisesRegexp(template.TemplateSyntaxError, 158 "'inclusion_one_default' received too many positional arguments", 159 template.Template, '{% load custom %}{% inclusion_one_default 37 42 56 %}') 160 161 self.assertRaisesRegexp(template.TemplateSyntaxError, 162 "'inclusion_one_default' did not receive value\(s\) for the argument\(s\): 'one'", 163 template.Template, '{% load custom %}{% inclusion_one_default %}') 164 165 self.assertRaisesRegexp(template.TemplateSyntaxError, 166 "'inclusion_unlimited_args' did not receive value\(s\) for the argument\(s\): 'one'", 167 template.Template, '{% load custom %}{% inclusion_unlimited_args %}') 168 169 t = template.Template('{% load custom %}{% inclusion_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 %}') 170 self.assertEqual(t.render(c), u'inclusion_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4\n') 171 172 self.assertRaisesRegexp(template.TemplateSyntaxError, 173 "'inclusion_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)", 174 template.Template, '{% load custom %}{% inclusion_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 %}') 175 176 self.assertRaisesRegexp(template.TemplateSyntaxError, 177 "'inclusion_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'", 178 template.Template, '{% load custom %}{% inclusion_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" %}') 179 180 def test_include_tag_missing_context(self): 181 # The 'context' parameter must be present when takes_context is True 182 self.assertRaisesRegexp(template.TemplateSyntaxError, 183 "'inclusion_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'", 184 template.Template, '{% load custom %}{% inclusion_tag_without_context_parameter 123 %}') 185 74 186 def test_inclusion_tags_from_template(self): 75 187 c = template.Context({'value': 42}) 76 188 … … class CustomTagTests(TestCase): 89 201 t = template.Template('{% load custom %}{% inclusion_params_and_context_from_template 37 %}') 90 202 self.assertEqual(t.render(c), u'inclusion_params_and_context_from_template - Expected result (context value: 42): 37\n') 91 203 204 t = template.Template('{% load custom %}{% inclusion_two_params_from_template 37 42 %}') 205 self.assertEqual(t.render(c), u'inclusion_two_params_from_template - Expected result: 37, 42\n') 206 207 t = template.Template('{% load custom %}{% inclusion_one_default_from_template 37 %}') 208 self.assertEqual(t.render(c), u'inclusion_one_default_from_template - Expected result: 37, hi\n') 209 210 t = template.Template('{% load custom %}{% inclusion_one_default_from_template 37 42 %}') 211 self.assertEqual(t.render(c), u'inclusion_one_default_from_template - Expected result: 37, 42\n') 212 213 t = template.Template('{% load custom %}{% inclusion_unlimited_args_from_template 37 %}') 214 self.assertEqual(t.render(c), u'inclusion_unlimited_args_from_template - Expected result: 37, hi\n') 215 216 t = template.Template('{% load custom %}{% inclusion_unlimited_args_from_template 37 42 56 89 %}') 217 self.assertEqual(t.render(c), u'inclusion_unlimited_args_from_template - Expected result: 37, 42, 56, 89\n') 218 219 t = template.Template('{% load custom %}{% inclusion_only_unlimited_args_from_template %}') 220 self.assertEqual(t.render(c), u'inclusion_only_unlimited_args_from_template - Expected result: \n') 221 222 t = template.Template('{% load custom %}{% inclusion_only_unlimited_args_from_template 37 42 56 89 %}') 223 self.assertEqual(t.render(c), u'inclusion_only_unlimited_args_from_template - Expected result: 37, 42, 56, 89\n') 224 92 225 def test_inclusion_tag_registration(self): 93 226 # Test that the decorators preserve the decorated function's docstring, name and attributes. 94 227 self.verify_tag(custom.inclusion_no_params, 'inclusion_no_params') … … class CustomTagTests(TestCase): 96 229 self.verify_tag(custom.inclusion_explicit_no_context, 'inclusion_explicit_no_context') 97 230 self.verify_tag(custom.inclusion_no_params_with_context, 'inclusion_no_params_with_context') 98 231 self.verify_tag(custom.inclusion_params_and_context, 'inclusion_params_and_context') 232 self.verify_tag(custom.inclusion_two_params, 'inclusion_two_params') 233 self.verify_tag(custom.inclusion_one_default, 'inclusion_one_default') 234 self.verify_tag(custom.inclusion_unlimited_args, 'inclusion_unlimited_args') 235 self.verify_tag(custom.inclusion_only_unlimited_args, 'inclusion_only_unlimited_args') 236 self.verify_tag(custom.inclusion_tag_without_context_parameter, 'inclusion_tag_without_context_parameter') 237 self.verify_tag(custom.inclusion_tag_use_l10n, 'inclusion_tag_use_l10n') 238 self.verify_tag(custom.inclusion_tag_current_app, 'inclusion_tag_current_app') 239 self.verify_tag(custom.inclusion_unlimited_args_kwargs, 'inclusion_unlimited_args_kwargs') 99 240 100 241 def test_15070_current_app(self): 101 242 """ … … class CustomTagTests(TestCase): 139 280 t = template.Template('{% load custom %}{% assignment_params_and_context 37 as var %}The result is: {{ var }}') 140 281 self.assertEqual(t.render(c), u'The result is: assignment_params_and_context - Expected result (context value: 42): 37') 141 282 283 t = template.Template('{% load custom %}{% assignment_two_params 37 42 as var %}The result is: {{ var }}') 284 self.assertEqual(t.render(c), u'The result is: assignment_two_params - Expected result: 37, 42') 285 286 t = template.Template('{% load custom %}{% assignment_one_default 37 as var %}The result is: {{ var }}') 287 self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 37, hi') 288 289 t = template.Template('{% load custom %}{% assignment_one_default 37 two="hello" as var %}The result is: {{ var }}') 290 self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 37, hello') 291 292 t = template.Template('{% load custom %}{% assignment_one_default one=99 two="hello" as var %}The result is: {{ var }}') 293 self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 99, hello') 294 295 self.assertRaisesRegexp(template.TemplateSyntaxError, 296 "'assignment_one_default' received unexpected keyword argument 'three'", 297 template.Template, '{% load custom %}{% assignment_one_default 99 two="hello" three="foo" as var %}') 298 299 t = template.Template('{% load custom %}{% assignment_one_default 37 42 as var %}The result is: {{ var }}') 300 self.assertEqual(t.render(c), u'The result is: assignment_one_default - Expected result: 37, 42') 301 302 t = template.Template('{% load custom %}{% assignment_unlimited_args 37 as var %}The result is: {{ var }}') 303 self.assertEqual(t.render(c), u'The result is: assignment_unlimited_args - Expected result: 37, hi') 304 305 t = template.Template('{% load custom %}{% assignment_unlimited_args 37 42 56 89 as var %}The result is: {{ var }}') 306 self.assertEqual(t.render(c), u'The result is: assignment_unlimited_args - Expected result: 37, 42, 56, 89') 307 308 t = template.Template('{% load custom %}{% assignment_only_unlimited_args as var %}The result is: {{ var }}') 309 self.assertEqual(t.render(c), u'The result is: assignment_only_unlimited_args - Expected result: ') 310 311 t = template.Template('{% load custom %}{% assignment_only_unlimited_args 37 42 56 89 as var %}The result is: {{ var }}') 312 self.assertEqual(t.render(c), u'The result is: assignment_only_unlimited_args - Expected result: 37, 42, 56, 89') 313 142 314 self.assertRaisesRegexp(template.TemplateSyntaxError, 143 315 "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'", 144 316 template.Template, '{% load custom %}{% assignment_one_param 37 %}The result is: {{ var }}') … … class CustomTagTests(TestCase): 151 323 "'assignment_one_param' tag takes at least 2 arguments and the second last argument must be 'as'", 152 324 template.Template, '{% load custom %}{% assignment_one_param 37 ass var %}The result is: {{ var }}') 153 325 326 self.assertRaisesRegexp(template.TemplateSyntaxError, 327 "'assignment_two_params' received too many positional arguments", 328 template.Template, '{% load custom %}{% assignment_two_params 37 42 56 as var %}The result is: {{ var }}') 329 330 self.assertRaisesRegexp(template.TemplateSyntaxError, 331 "'assignment_one_default' received too many positional arguments", 332 template.Template, '{% load custom %}{% assignment_one_default 37 42 56 as var %}The result is: {{ var }}') 333 334 self.assertRaisesRegexp(template.TemplateSyntaxError, 335 "'assignment_one_default' did not receive value\(s\) for the argument\(s\): 'one'", 336 template.Template, '{% load custom %}{% assignment_one_default as var %}The result is: {{ var }}') 337 338 self.assertRaisesRegexp(template.TemplateSyntaxError, 339 "'assignment_unlimited_args' did not receive value\(s\) for the argument\(s\): 'one'", 340 template.Template, '{% load custom %}{% assignment_unlimited_args as var %}The result is: {{ var }}') 341 342 t = template.Template('{% load custom %}{% assignment_unlimited_args_kwargs 37 40|add:2 56 eggs="scrambled" four=1|add:3 as var %}The result is: {{ var }}') 343 self.assertEqual(t.render(c), u'The result is: assignment_unlimited_args_kwargs - Expected result: 37, 42, 56 / eggs=scrambled, four=4') 344 345 self.assertRaisesRegexp(template.TemplateSyntaxError, 346 "'assignment_unlimited_args_kwargs' received some positional argument\(s\) after some keyword argument\(s\)", 347 template.Template, '{% load custom %}{% assignment_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 as var %}The result is: {{ var }}') 348 349 self.assertRaisesRegexp(template.TemplateSyntaxError, 350 "'assignment_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'", 351 template.Template, '{% load custom %}{% assignment_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" as var %}The result is: {{ var }}') 352 154 353 def test_assignment_tag_registration(self): 155 354 # Test that the decorators preserve the decorated function's docstring, name and attributes. 156 355 self.verify_tag(custom.assignment_no_params, 'assignment_no_params') … … class CustomTagTests(TestCase): 158 357 self.verify_tag(custom.assignment_explicit_no_context, 'assignment_explicit_no_context') 159 358 self.verify_tag(custom.assignment_no_params_with_context, 'assignment_no_params_with_context') 160 359 self.verify_tag(custom.assignment_params_and_context, 'assignment_params_and_context') 360 self.verify_tag(custom.assignment_one_default, 'assignment_one_default') 361 self.verify_tag(custom.assignment_two_params, 'assignment_two_params') 362 self.verify_tag(custom.assignment_unlimited_args, 'assignment_unlimited_args') 363 self.verify_tag(custom.assignment_only_unlimited_args, 'assignment_only_unlimited_args') 364 self.verify_tag(custom.assignment_unlimited_args, 'assignment_unlimited_args') 365 self.verify_tag(custom.assignment_unlimited_args_kwargs, 'assignment_unlimited_args_kwargs') 366 self.verify_tag(custom.assignment_tag_without_context_parameter, 'assignment_tag_without_context_parameter') 161 367 162 368 def test_assignment_tag_missing_context(self): 163 # That the 'context' parameter must be present when takes_context is True 164 def an_assignment_tag_without_parameters(arg): 165 """Expected __doc__""" 166 return "Expected result" 167 168 register = template.Library() 169 decorator = register.assignment_tag(takes_context=True) 170 369 # The 'context' parameter must be present when takes_context is True 171 370 self.assertRaisesRegexp(template.TemplateSyntaxError, 172 " Any tag function decorated with takes_context=Truemust have a first argument of 'context'",173 decorator, an_assignment_tag_without_parameters)371 "'assignment_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'", 372 template.Template, '{% load custom %}{% assignment_tag_without_context_parameter 123 as var %}') -
tests/regressiontests/templates/templatetags/custom.py
diff --git a/tests/regressiontests/templates/templatetags/custom.py b/tests/regressiontests/templates/templatetags/custom.py index dfa4171..0e07d53 100644
a b 1 1 from django import template 2 2 from django.template.defaultfilters import stringfilter 3 3 from django.template.loader import get_template 4 import operator 4 5 5 6 register = template.Library() 6 7 … … def params_and_context(context, arg): 40 41 return "params_and_context - Expected result (context value: %s): %s" % (context['value'], arg) 41 42 params_and_context.anything = "Expected params_and_context __dict__" 42 43 44 @register.simple_tag 45 def simple_two_params(one, two): 46 """Expected simple_two_params __doc__""" 47 return "simple_two_params - Expected result: %s, %s" % (one, two) 48 simple_two_params.anything = "Expected simple_two_params __dict__" 49 50 @register.simple_tag 51 def simple_one_default(one, two='hi'): 52 """Expected simple_one_default __doc__""" 53 return "simple_one_default - Expected result: %s, %s" % (one, two) 54 simple_one_default.anything = "Expected simple_one_default __dict__" 55 56 @register.simple_tag 57 def simple_unlimited_args(one, two='hi', *args): 58 """Expected simple_unlimited_args __doc__""" 59 return "simple_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)])) 60 simple_unlimited_args.anything = "Expected simple_unlimited_args __dict__" 61 62 @register.simple_tag 63 def simple_only_unlimited_args(*args): 64 """Expected simple_only_unlimited_args __doc__""" 65 return "simple_only_unlimited_args - Expected result: %s" % ', '.join([unicode(arg) for arg in args]) 66 simple_only_unlimited_args.anything = "Expected simple_only_unlimited_args __dict__" 67 68 @register.simple_tag 69 def simple_unlimited_args_kwargs(one, two='hi', *args, **kwargs): 70 """Expected simple_unlimited_args_kwargs __doc__""" 71 # Sort the dictionary by key to guarantee the order for testing. 72 sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0)) 73 return "simple_unlimited_args_kwargs - Expected result: %s / %s" % ( 74 ', '.join([unicode(arg) for arg in [one, two] + list(args)]), 75 ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg]) 76 ) 77 simple_unlimited_args_kwargs.anything = "Expected simple_unlimited_args_kwargs __dict__" 78 79 @register.simple_tag(takes_context=True) 80 def simple_tag_without_context_parameter(arg): 81 """Expected simple_tag_without_context_parameter __doc__""" 82 return "Expected result" 83 simple_tag_without_context_parameter.anything = "Expected simple_tag_without_context_parameter __dict__" 84 85 @register.simple_tag(takes_context=True) 86 def current_app(context): 87 return "%s" % context.current_app 88 89 @register.simple_tag(takes_context=True) 90 def use_l10n(context): 91 return "%s" % context.use_l10n 92 93 @register.simple_tag(name='minustwo') 94 def minustwo_overridden_name(value): 95 return value - 2 96 97 register.simple_tag(lambda x: x - 1, name='minusone') 98 43 99 @register.inclusion_tag('inclusion.html') 44 100 def inclusion_no_params(): 45 101 """Expected inclusion_no_params __doc__""" … … def inclusion_params_and_context_from_template(context, arg): 100 156 return {"result" : "inclusion_params_and_context_from_template - Expected result (context value: %s): %s" % (context['value'], arg)} 101 157 inclusion_params_and_context_from_template.anything = "Expected inclusion_params_and_context_from_template __dict__" 102 158 103 @register.simple_tag(takes_context=True) 104 def current_app(context): 105 return "%s" % context.current_app 159 @register.inclusion_tag('inclusion.html') 160 def inclusion_two_params(one, two): 161 """Expected inclusion_two_params __doc__""" 162 return {"result": "inclusion_two_params - Expected result: %s, %s" % (one, two)} 163 inclusion_two_params.anything = "Expected inclusion_two_params __dict__" 164 165 @register.inclusion_tag(get_template('inclusion.html')) 166 def inclusion_two_params_from_template(one, two): 167 """Expected inclusion_two_params_from_template __doc__""" 168 return {"result": "inclusion_two_params_from_template - Expected result: %s, %s" % (one, two)} 169 inclusion_two_params_from_template.anything = "Expected inclusion_two_params_from_template __dict__" 170 171 @register.inclusion_tag('inclusion.html') 172 def inclusion_one_default(one, two='hi'): 173 """Expected inclusion_one_default __doc__""" 174 return {"result": "inclusion_one_default - Expected result: %s, %s" % (one, two)} 175 inclusion_one_default.anything = "Expected inclusion_one_default __dict__" 176 177 @register.inclusion_tag(get_template('inclusion.html')) 178 def inclusion_one_default_from_template(one, two='hi'): 179 """Expected inclusion_one_default_from_template __doc__""" 180 return {"result": "inclusion_one_default_from_template - Expected result: %s, %s" % (one, two)} 181 inclusion_one_default_from_template.anything = "Expected inclusion_one_default_from_template __dict__" 182 183 @register.inclusion_tag('inclusion.html') 184 def inclusion_unlimited_args(one, two='hi', *args): 185 """Expected inclusion_unlimited_args __doc__""" 186 return {"result": "inclusion_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))} 187 inclusion_unlimited_args.anything = "Expected inclusion_unlimited_args __dict__" 188 189 @register.inclusion_tag(get_template('inclusion.html')) 190 def inclusion_unlimited_args_from_template(one, two='hi', *args): 191 """Expected inclusion_unlimited_args_from_template __doc__""" 192 return {"result": "inclusion_unlimited_args_from_template - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)]))} 193 inclusion_unlimited_args_from_template.anything = "Expected inclusion_unlimited_args_from_template __dict__" 194 195 @register.inclusion_tag('inclusion.html') 196 def inclusion_only_unlimited_args(*args): 197 """Expected inclusion_only_unlimited_args __doc__""" 198 return {"result": "inclusion_only_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in args]))} 199 inclusion_only_unlimited_args.anything = "Expected inclusion_only_unlimited_args __dict__" 200 201 @register.inclusion_tag(get_template('inclusion.html')) 202 def inclusion_only_unlimited_args_from_template(*args): 203 """Expected inclusion_only_unlimited_args_from_template __doc__""" 204 return {"result": "inclusion_only_unlimited_args_from_template - Expected result: %s" % (', '.join([unicode(arg) for arg in args]))} 205 inclusion_only_unlimited_args_from_template.anything = "Expected inclusion_only_unlimited_args_from_template __dict__" 106 206 107 207 @register.inclusion_tag('test_incl_tag_current_app.html', takes_context=True) 108 208 def inclusion_tag_current_app(context): 209 """Expected inclusion_tag_current_app __doc__""" 109 210 return {} 110 111 @register.simple_tag(takes_context=True) 112 def use_l10n(context): 113 return "%s" % context.use_l10n 211 inclusion_tag_current_app.anything = "Expected inclusion_tag_current_app __dict__" 114 212 115 213 @register.inclusion_tag('test_incl_tag_use_l10n.html', takes_context=True) 116 214 def inclusion_tag_use_l10n(context): 215 """Expected inclusion_tag_use_l10n __doc__""" 117 216 return {} 217 inclusion_tag_use_l10n.anything = "Expected inclusion_tag_use_l10n __dict__" 218 219 @register.inclusion_tag('inclusion.html') 220 def inclusion_unlimited_args_kwargs(one, two='hi', *args, **kwargs): 221 """Expected inclusion_unlimited_args_kwargs __doc__""" 222 # Sort the dictionary by key to guarantee the order for testing. 223 sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0)) 224 return {"result": "inclusion_unlimited_args_kwargs - Expected result: %s / %s" % ( 225 ', '.join([unicode(arg) for arg in [one, two] + list(args)]), 226 ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg]) 227 )} 228 inclusion_unlimited_args_kwargs.anything = "Expected inclusion_unlimited_args_kwargs __dict__" 229 230 @register.inclusion_tag('inclusion.html', takes_context=True) 231 def inclusion_tag_without_context_parameter(arg): 232 """Expected inclusion_tag_without_context_parameter __doc__""" 233 return {} 234 inclusion_tag_without_context_parameter.anything = "Expected inclusion_tag_without_context_parameter __dict__" 118 235 119 236 @register.assignment_tag 120 237 def assignment_no_params(): … … def assignment_params_and_context(context, arg): 146 263 return "assignment_params_and_context - Expected result (context value: %s): %s" % (context['value'], arg) 147 264 assignment_params_and_context.anything = "Expected assignment_params_and_context __dict__" 148 265 149 register.simple_tag(lambda x: x - 1, name='minusone') 266 @register.assignment_tag 267 def assignment_two_params(one, two): 268 """Expected assignment_two_params __doc__""" 269 return "assignment_two_params - Expected result: %s, %s" % (one, two) 270 assignment_two_params.anything = "Expected assignment_two_params __dict__" 150 271 151 @register.simple_tag(name='minustwo') 152 def minustwo_overridden_name(value): 153 return value - 2 272 @register.assignment_tag 273 def assignment_one_default(one, two='hi'): 274 """Expected assignment_one_default __doc__""" 275 return "assignment_one_default - Expected result: %s, %s" % (one, two) 276 assignment_one_default.anything = "Expected assignment_one_default __dict__" 277 278 @register.assignment_tag 279 def assignment_unlimited_args(one, two='hi', *args): 280 """Expected assignment_unlimited_args __doc__""" 281 return "assignment_unlimited_args - Expected result: %s" % (', '.join([unicode(arg) for arg in [one, two] + list(args)])) 282 assignment_unlimited_args.anything = "Expected assignment_unlimited_args __dict__" 283 284 @register.assignment_tag 285 def assignment_only_unlimited_args(*args): 286 """Expected assignment_only_unlimited_args __doc__""" 287 return "assignment_only_unlimited_args - Expected result: %s" % ', '.join([unicode(arg) for arg in args]) 288 assignment_only_unlimited_args.anything = "Expected assignment_only_unlimited_args __dict__" 289 290 @register.assignment_tag 291 def assignment_unlimited_args_kwargs(one, two='hi', *args, **kwargs): 292 """Expected assignment_unlimited_args_kwargs __doc__""" 293 # Sort the dictionary by key to guarantee the order for testing. 294 sorted_kwarg = sorted(kwargs.iteritems(), key=operator.itemgetter(0)) 295 return "assignment_unlimited_args_kwargs - Expected result: %s / %s" % ( 296 ', '.join([unicode(arg) for arg in [one, two] + list(args)]), 297 ', '.join(['%s=%s' % (k, v) for (k, v) in sorted_kwarg]) 298 ) 299 assignment_unlimited_args_kwargs.anything = "Expected assignment_unlimited_args_kwargs __dict__" 300 301 @register.assignment_tag(takes_context=True) 302 def assignment_tag_without_context_parameter(arg): 303 """Expected assignment_tag_without_context_parameter __doc__""" 304 return "Expected result" 305 assignment_tag_without_context_parameter.anything = "Expected assignment_tag_without_context_parameter __dict__" 306 No newline at end of file