Ticket #20434: patch1.patch
File patch1.patch, 35.1 KB (added by , 11 years ago) |
---|
-
AUTHORS
commit f5255b1672a60023c82a035f25cc679dc299fed2 Author: Jonathan Slenders <jonathan@slenders.be> Date: Tue Jun 4 17:35:58 2013 +0200 - Introduction of the TemplateTag class and Grammar class. - SimpleTag, InclusionTag and AssignmentTag automatically become a TemplateTag class - LibraryGrammar for extraction of the grammar of a template tag library. - Also added unit tests. diff --git a/AUTHORS b/AUTHORS index 80f2779..7c4e012 100644
a b answer newbie questions, and generally made Django that much better: 634 634 Gasper Zejn <zejn@kiberpipa.org> 635 635 Jarek Zgoda <jarek.zgoda@gmail.com> 636 636 Cheng Zhang 637 Jonathan Slenders 637 638 638 639 A big THANK YOU goes to: 639 640 -
django/template/__init__.py
diff --git a/django/template/__init__.py b/django/template/__init__.py index ca1bd49..0e55d62 100644
a b from django.template.base import (Context, FilterExpression, Lexer, Node, 70 70 71 71 # Compiling templates 72 72 from django.template.base import (compile_string, resolve_variable, 73 unescape_string_literal , generic_tag_compiler)73 unescape_string_literal) 74 74 75 75 # Library management 76 76 from django.template.base import (Library, add_to_builtins, builtins, 77 77 get_library, get_templatetags_modules, get_text_list, import_library, 78 78 libraries) 79 79 80 # Class based templates 81 from django.template.generic import Grammar, UnknownGrammar, GrammarException, TemplateTag 82 80 83 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string') -
django/template/base.py
diff --git a/django/template/base.py b/django/template/base.py index dc6b0c3..2de407c 100644
a b 1 1 from __future__ import absolute_import, unicode_literals 2 2 3 import inspect 3 4 import re 4 5 from functools import partial 5 6 from inspect import getargspec … … def parse_bits(parser, bits, params, varargs, varkw, defaults, 1012 1013 (name, ", ".join(["'%s'" % p for p in unhandled_params]))) 1013 1014 return args, kwargs 1014 1015 1015 def generic_tag_compiler(parser, token, params, varargs, varkw, defaults, 1016 name, takes_context, node_class): 1016 def _get_resolved_arguments(args, kwargs, takes_context, context): 1017 1017 """ 1018 Returns a template.Node subclass. 1018 A helper for tags such as SimpleTag, InclusionTag and AssignmentTag. 1019 Manages the positional and keyword arguments to be passed to the decorated 1020 function. 1019 1021 """ 1020 bits = token.split_contents()[1:] 1021 args, kwargs = parse_bits(parser, bits, params, varargs, varkw, 1022 defaults, takes_context, name) 1023 return node_class(takes_context, args, kwargs) 1024 1025 class TagHelperNode(Node): 1026 """ 1027 Base class for tag helper nodes such as SimpleNode, InclusionNode and 1028 AssignmentNode. Manages the positional and keyword arguments to be passed 1029 to the decorated function. 1030 """ 1031 1032 def __init__(self, takes_context, args, kwargs): 1033 self.takes_context = takes_context 1034 self.args = args 1035 self.kwargs = kwargs 1036 1037 def get_resolved_arguments(self, context): 1038 resolved_args = [var.resolve(context) for var in self.args] 1039 if self.takes_context: 1040 resolved_args = [context] + resolved_args 1041 resolved_kwargs = dict((k, v.resolve(context)) 1042 for k, v in self.kwargs.items()) 1043 return resolved_args, resolved_kwargs 1022 resolved_args = [var.resolve(context) for var in args] 1023 if takes_context: 1024 resolved_args = [context] + resolved_args 1025 resolved_kwargs = dict((k, v.resolve(context)) 1026 for k, v in kwargs.items()) 1027 return resolved_args, resolved_kwargs 1044 1028 1045 1029 class Library(object): 1046 1030 def __init__(self): … … class Library(object): 1048 1032 self.tags = {} 1049 1033 1050 1034 def tag(self, name=None, compile_function=None): 1035 from django.template.generic import TemplateTag, UnknownGrammar 1051 1036 if name is None and compile_function is None: 1052 1037 # @register.tag() 1053 1038 return self.tag_function 1054 1039 elif name is not None and compile_function is None: 1055 1040 if callable(name): 1056 1041 # @register.tag 1057 return self.tag_function(name) 1042 if inspect.isclass(name) and issubclass(name, TemplateTag): 1043 # When a TemplateTag instance is registered, create a 1044 # compile_funcion from the class. It's nicer to handle this 1045 # separately than handling this class as any other 1046 # callable, and ending with the (parser, token) in the 1047 # constructor of TemplateTag. 1048 fname, func = name.as_compile_funcion() 1049 self.tag(name=fname, compile_function=func) 1050 return name 1051 else: 1052 return self.tag_function(name) 1058 1053 else: 1059 1054 # @register.tag('somename') or @register.tag(name='somename') 1060 1055 def dec(func): 1061 1056 return self.tag(name, func) 1062 1057 return dec 1063 1058 elif name is not None and compile_function is not None: 1064 # register.tag('somename', somefunc) 1059 # Make sure that every compile_function has a grammar instance 1060 # attached. 1061 if not hasattr(compile_function, 'grammar'): 1062 compile_function.grammar = UnknownGrammar(name) 1063 1065 1064 self.tags[name] = compile_function 1066 1065 return compile_function 1067 1066 else: … … class Library(object): 1069 1068 "Library.tag: (%r, %r)", (name, compile_function)) 1070 1069 1071 1070 def tag_function(self, func): 1072 self.tags[getattr(func, "_decorated_function", func).__name__] = func 1071 name = getattr(func, "_decorated_function", func).__name__ 1072 self.tag(name=name, compile_function=func) 1073 1073 return func 1074 1074 1075 1075 def filter(self, name=None, filter_func=None, **flags): … … class Library(object): 1111 1111 return self.filter(name, func, **flags) 1112 1112 1113 1113 def simple_tag(self, func=None, takes_context=None, name=None): 1114 from django.template.generic import TemplateTag, Grammar 1114 1115 def dec(func): 1115 1116 params, varargs, varkw, defaults = getargspec(func) 1116 1117 1117 class SimpleNode(TagHelperNode): 1118 function_name = (name or 1119 getattr(func, '_decorated_function', func).__name__) 1120 1121 class _SimpleTag(TemplateTag): 1122 __doc__ = func.__doc__ 1123 grammar = Grammar(function_name) 1124 1125 def __init__(self, parser, parse_result): 1126 self.args, self.kwargs = parse_bits(parser, parse_result.arguments, 1127 params, varargs, varkw, defaults, takes_context, function_name) 1118 1128 1119 1129 def render(self, context): 1120 resolved_args, resolved_kwargs = self.get_resolved_arguments(context) 1130 resolved_args, resolved_kwargs = _get_resolved_arguments(self.args, 1131 self.kwargs, takes_context, context) 1121 1132 return func(*resolved_args, **resolved_kwargs) 1122 1133 1123 function_name = (name or 1124 getattr(func, '_decorated_function', func).__name__) 1125 compile_func = partial(generic_tag_compiler, 1126 params=params, varargs=varargs, varkw=varkw, 1127 defaults=defaults, name=function_name, 1128 takes_context=takes_context, node_class=SimpleNode) 1129 compile_func.__doc__ = func.__doc__ 1130 self.tag(function_name, compile_func) 1134 self.tag(_SimpleTag) 1131 1135 return func 1132 1136 1133 1137 if func is None: … … class Library(object): 1140 1144 raise TemplateSyntaxError("Invalid arguments provided to simple_tag") 1141 1145 1142 1146 def assignment_tag(self, func=None, takes_context=None, name=None): 1147 from django.template.generic import TemplateTag, Grammar 1143 1148 def dec(func): 1144 1149 params, varargs, varkw, defaults = getargspec(func) 1145 1150 1146 class AssignmentNode(TagHelperNode): 1147 def __init__(self, takes_context, args, kwargs, target_var): 1148 super(AssignmentNode, self).__init__(takes_context, args, kwargs) 1149 self.target_var = target_var 1151 function_name = (name or 1152 getattr(func, '_decorated_function', func).__name__) 1153 1154 class _AssignmentTag(TemplateTag): 1155 grammar = Grammar(function_name) 1156 __doc__ = func.__doc__ 1157 1158 def __init__(self, parser, parse_result): 1159 bits = parse_result.arguments 1160 if len(bits) < 2 or bits[-2] != 'as': 1161 raise TemplateSyntaxError( 1162 "'%s' tag takes at least 2 arguments and the " 1163 "second last argument must be 'as'" % function_name) 1164 self.target_var = bits[-1] 1165 self.args, self.kwargs = parse_bits(parser, bits[:-2], 1166 params, varargs, varkw, defaults, takes_context, function_name) 1150 1167 1151 1168 def render(self, context): 1152 resolved_args, resolved_kwargs = self.get_resolved_arguments(context) 1169 resolved_args, resolved_kwargs = _get_resolved_arguments(self.args, 1170 self.kwargs, takes_context, context) 1153 1171 context[self.target_var] = func(*resolved_args, **resolved_kwargs) 1154 1172 return '' 1155 1173 1156 function_name = (name or 1157 getattr(func, '_decorated_function', func).__name__) 1158 1159 def compile_func(parser, token): 1160 bits = token.split_contents()[1:] 1161 if len(bits) < 2 or bits[-2] != 'as': 1162 raise TemplateSyntaxError( 1163 "'%s' tag takes at least 2 arguments and the " 1164 "second last argument must be 'as'" % function_name) 1165 target_var = bits[-1] 1166 bits = bits[:-2] 1167 args, kwargs = parse_bits(parser, bits, params, 1168 varargs, varkw, defaults, takes_context, function_name) 1169 return AssignmentNode(takes_context, args, kwargs, target_var) 1170 1171 compile_func.__doc__ = func.__doc__ 1172 self.tag(function_name, compile_func) 1174 self.tag(_AssignmentTag) 1173 1175 return func 1174 1176 1175 1177 if func is None: … … class Library(object): 1182 1184 raise TemplateSyntaxError("Invalid arguments provided to assignment_tag") 1183 1185 1184 1186 def inclusion_tag(self, file_name, context_class=Context, takes_context=False, name=None): 1187 from django.template.generic import TemplateTag, Grammar 1185 1188 def dec(func): 1186 1189 params, varargs, varkw, defaults = getargspec(func) 1187 1190 1188 class InclusionNode(TagHelperNode): 1191 function_name = (name or 1192 getattr(func, '_decorated_function', func).__name__) 1193 1194 class _InclusionTag(TemplateTag): 1195 grammar = Grammar(function_name) 1196 __doc__ = func.__doc__ 1197 1198 def __init__(self, parser, parse_result): 1199 self.args, self.kwargs = parse_bits(parser, parse_result.arguments, 1200 params, varargs, varkw, defaults, takes_context, function_name) 1189 1201 1190 1202 def render(self, context): 1191 resolved_args, resolved_kwargs = self.get_resolved_arguments(context) 1203 resolved_args, resolved_kwargs = _get_resolved_arguments(self.args, 1204 self.kwargs, takes_context, context) 1205 1192 1206 _dict = func(*resolved_args, **resolved_kwargs) 1193 1207 1194 if not getattr(self, ' nodelist', False):1208 if not getattr(self, '_nodelist', False): 1195 1209 from django.template.loader import get_template, select_template 1196 1210 if isinstance(file_name, Template): 1197 1211 t = file_name … … class Library(object): 1199 1213 t = select_template(file_name) 1200 1214 else: 1201 1215 t = get_template(file_name) 1202 self. nodelist = t.nodelist1216 self._nodelist = t.nodelist 1203 1217 new_context = context_class(_dict, **{ 1204 1218 'autoescape': context.autoescape, 1205 1219 'current_app': context.current_app, … … class Library(object): 1213 1227 csrf_token = context.get('csrf_token', None) 1214 1228 if csrf_token is not None: 1215 1229 new_context['csrf_token'] = csrf_token 1216 return self. nodelist.render(new_context)1230 return self._nodelist.render(new_context) 1217 1231 1218 function_name = (name or 1219 getattr(func, '_decorated_function', func).__name__) 1220 compile_func = partial(generic_tag_compiler, 1221 params=params, varargs=varargs, varkw=varkw, 1222 defaults=defaults, name=function_name, 1223 takes_context=takes_context, node_class=InclusionNode) 1224 compile_func.__doc__ = func.__doc__ 1225 self.tag(function_name, compile_func) 1232 self.tag(_InclusionTag) 1226 1233 return func 1227 1234 return dec 1228 1235 1236 def get_grammar(self): 1237 return LibraryGrammar(self) 1238 1239 1240 class LibraryGrammar(object): 1241 def __init__(self, library=None): 1242 self._tags = [] 1243 1244 if library: 1245 self.add_library(library) 1246 1247 def add_library(self, library): 1248 for name, compile_func in library.tags.items(): 1249 grammar = compile_func.grammar 1250 self._tags.append((grammar.first_tagname, grammar.as_string())) 1251 1252 self._tags = sorted(self._tags, key=lambda i:i[0]) 1253 1254 def as_string(self, style=None): 1255 """ 1256 Return a BNF like notation. 1257 """ 1258 max_length = max(len(i[0]) for i in self._tags) 1259 format = '%%-%ss ::= %%s' % max_length 1260 1261 return '\n'.join(format % (g[0], g[1]) for g in self._tags) 1262 1263 @classmethod 1264 def from_builtins(cls): 1265 library_grammar = cls() 1266 for b in builtins: 1267 library_grammar.add_library(b) 1268 return library_grammar 1269 1270 1229 1271 def is_library_missing(name): 1230 1272 """Check if library that failed to load cannot be found under any 1231 1273 templatetags directory or does exist but fails to import. -
new file django/template/generic.py
diff --git a/django/template/generic.py b/django/template/generic.py new file mode 100644 index 0000000..11964e4
- + 1 from django.template.base import Node, NodeList, TemplateSyntaxError 2 import re 3 4 5 class GrammarException(Exception): 6 pass 7 8 9 class Grammar(object): 10 """ 11 Generic template tag grammar. 12 13 A grammar is defined by a space separated list of tagnames, optionally with 14 a modifier to indicate how many times this tag can appear: 15 - *: zero or more times; 16 - +: one or more times; 17 - ?: zero or one time; 18 - (no suffix): exactly one time. 19 20 For instance: 'if elif* else? endif' 21 22 The constraints are: 23 - The first and last template tags cannot have any modifier. 24 - A tagname should not appear more than once in a single grammar. 25 26 This simple grammar does not automatically parse or validate template tag 27 parameters. 28 """ 29 tagname_re = re.compile(r"^[a-zA-Z0-9_]+$") 30 31 class ParseResult(object): 32 def __init__(self, parser, parts): 33 self.parser = parser 34 self.parts = parts 35 36 self.nodelist = NodeList(node for p in parts for node in p.nodelist) 37 38 @property 39 def tagname(self): 40 return self.parts[0].name 41 42 @property 43 def arguments(self): 44 return self.parts[0].arguments 45 46 class TemplateTagPart(object): 47 def __init__(self, token, name, arguments, nodelist): 48 self.token = token 49 self.name = name 50 self.arguments = arguments 51 self.nodelist = nodelist 52 53 def __init__(self, grammar, _split_contents=True): 54 # XXX _split_contents is for internal use only. 55 if not grammar: 56 raise GrammarException('No template tags given.') 57 58 result = [] 59 for tag in grammar.split(): 60 if tag.endswith('*') or tag.endswith('+') or tag.endswith('?'): 61 tagname, flag = tag[:-1], tag[-1] 62 else: 63 tagname, flag = tag, None 64 65 if not self.tagname_re.match(tagname): 66 raise GrammarException('%s is not a valid template tag name' % tagname) 67 68 if tagname in result: 69 raise GrammarException('The template tag %s is defined more than once.' % tagname) 70 71 result.append(tagname) 72 result.append(flag) 73 74 # Validate grammar 75 if not result: 76 raise GrammarException('No template tags given.') 77 if result[1] != None: 78 raise GrammarException('The first template tag should not repeat.') 79 if result[-1] != None: 80 raise GrammarException('The last template tag should not repeat.') 81 82 self._split_contents = _split_contents 83 self._grammar = result 84 self._grammar_string = grammar 85 86 self.first_tagname = result[0] 87 88 @property 89 def is_simple(self): 90 """ True when this grammar consists of a single tag only. """ 91 return len(self._grammar) == 2 and self._grammar[1] == None 92 93 def as_string(self, style=None): 94 """ 95 Return a BNF like notation for this grammar. 96 """ 97 return self._grammar_string 98 99 def parse(self, parser, token): 100 """ 101 Apply this generic parser, on a token input, and return a ParseResult. 102 """ 103 parts = [] 104 _grammar = self._grammar[:] 105 106 while _grammar: 107 if token.contents.startswith(_grammar[0]): 108 name = _grammar[0] 109 current_token = token 110 arguments = self.parse_arguments(name, token) 111 112 if len(_grammar) == 2: 113 # Last tag 114 nodelist = NodeList() 115 _grammar = [] 116 else: 117 if _grammar[1] == '+': 118 _grammar[1] = '*' 119 120 elif _grammar[1] in (None, '?'): 121 _grammar = _grammar[2:] 122 123 nodelist = parser.parse(_grammar[::2]) 124 token = parser.next_token() 125 126 parts.append(self.TemplateTagPart(current_token, name, arguments, nodelist)) 127 128 elif _grammar[1] in ('?', '*'): 129 # No match, pop grammar, and try again. 130 _grammar = _grammar[2:] 131 132 else: 133 # No match. Invalid template. 134 raise TemplateSyntaxError('Expected "%s" template tag, but found "%s" instead.' % 135 (_grammar[0], token.contents)) 136 137 return self.ParseResult(parser, parts) 138 139 def parse_arguments(self, tagname, token): 140 if self._split_contents: 141 return token.split_contents()[1:] 142 try: 143 return token.contents.split(None, 1)[1] 144 except IndexError: 145 return '' 146 147 148 class UnknownGrammar(Grammar): 149 # Grammar-compatible class which will be attached to compile_functions that 150 # don't have a grammar yet. 151 def __init__(self, tagname): 152 self.first_tagname = tagname 153 self._grammar_string = '<Unknown Grammar>' 154 155 def parse(self, parser, token): 156 raise NotImplementedError 157 158 def as_string(self): 159 return self._grammar_string 160 161 162 class TemplateTag(Node): 163 """ 164 Generic template tag. 165 """ 166 grammar = None 167 168 @classmethod 169 def as_compile_funcion(cls): 170 if not cls.grammar: 171 raise TemplateSyntaxError('TemplateTag has no valid grammar.') 172 def compile_function(parser, token): 173 return cls.parse(parser, token) 174 compile_function.grammar = cls.grammar 175 compile_function.__name__ = str(cls.grammar.first_tagname) 176 compile_function.__doc__ = cls.__doc__ 177 return cls.grammar.first_tagname, compile_function 178 179 @classmethod 180 def parse(cls, parser, token): 181 """ Turn parser/token into Node """ 182 parse_result = cls.grammar.parse(parser, token) 183 node = cls.handle_parse_result(parser, parse_result) 184 node.parse_result = parse_result 185 return node 186 187 @classmethod 188 def handle_parse_result(cls, parser, parse_result): 189 """ Create Node instance from parse result. """ 190 node = cls(parser, parse_result) 191 return node 192 193 def __init__(self, parser, parse_result): 194 pass 195 196 def __iter__(self): 197 for node in self.nodelist: 198 yield node 199 200 @property 201 def nodelist(self): 202 return self.parse_result.nodelist 203 204 def render(self, context): 205 return self.nodelist.render(context) -
new file tests/template_tests/templatetags/class_based.py
diff --git a/tests/template_tests/templatetags/class_based.py b/tests/template_tests/templatetags/class_based.py new file mode 100644 index 0000000..f0543f5
- + 1 import operator 2 3 from django import template 4 from django.template.defaultfilters import stringfilter 5 from django.template.loader import get_template 6 from django.template.generic import TemplateTag, Grammar 7 from django.utils import six 8 9 register = template.Library() 10 11 12 13 @register.tag 14 class OneTag(TemplateTag): 15 """ 16 A simple templatetag which does nothing actually. 17 {% cb_one_tag %} 18 """ 19 grammar = Grammar('cb_one_tag') 20 21 @register.tag 22 class TwoTags(TemplateTag): 23 """ 24 A templatetag which just renders the containing nodes. 25 {% cb_two_tags %} ... {% end_cb_two_tags %} 26 """ 27 grammar = Grammar('cb_two_tags end_cb_two_tags') 28 29 @register.tag 30 class ToUpper(TemplateTag): 31 """ 32 A template tags which converts the inner noders to uppercase 33 after rendering. 34 {% cb_to_upper %} ... {% end_cb_to_upper %} 35 """ 36 grammar = Grammar('cb_to_upper end_cb_to_upper') 37 38 def render(self, context): 39 return self.nodelist.render(context).upper() 40 41 @register.tag 42 class WithMiddleTag(TemplateTag): 43 """ 44 A template tags pair which has a middle tag. 45 {% cb_with_middle %} ... {% middle %} ... {% end_cb_with_middle %} 46 """ 47 grammar = Grammar('cb_with_middle middle end_cb_with_middle') 48 49 @register.tag 50 class RenderSecondNode(TemplateTag): 51 """ 52 A template tags which converts the inner noders to uppercase 53 after rendering. 54 {% cb_render_second %} ... {% middle %} ... {% end_cb_render_second %} 55 """ 56 grammar = Grammar('cb_render_second middle end_cb_render_second') 57 58 def render(self, context): 59 return self.parse_result.parts[1].nodelist.render(context) 60 61 @register.tag 62 class RenderSecondNode2(TemplateTag): 63 """ 64 Another approach. 65 {% cb_render_second2 %} ... {% middle %} ... {% end_cb_render_second2 %} 66 """ 67 grammar = Grammar('cb_render_second2 middle end_cb_render_second2') 68 69 def __init__(self, parser, parse_result): 70 self.second_nodelist = parse_result.parts[1].nodelist 71 72 def render(self, context): 73 return self.second_nodelist.render(context) 74 75 @register.tag 76 class OptionalMiddle(TemplateTag): 77 """ 78 The middle tag optional. Just renders everything. 79 {% cb_optional_middle %} ... {% middle %} ... {% end_cb_optional_middle %} 80 """ 81 grammar = Grammar('cb_optional_middle middle? end_cb_optional_middle') 82 83 @register.tag 84 class RepeatingMiddle(TemplateTag): 85 """ 86 The middle tag repeatable 0..n times. 87 """ 88 grammar = Grammar('cb_repeating_middle middle* end_cb_repeating_middle') 89 90 @register.tag 91 class OneOrMoreMidleMiddle(TemplateTag): 92 """ 93 The middle tag repeatable 1..n times. 94 """ 95 grammar = Grammar('cb_one_or_more_middle middle+ end_cb_one_or_more_middle') 96 97 @register.tag 98 class ReverseBlocks(TemplateTag): 99 """ 100 Reverse blocks: render the last parst first and the first part last. 101 {% cb_reverse_blocks %} ... {% next %} ... {% next %} ... {% end_cb_reverse_blocks %} 102 """ 103 grammar = Grammar('cb_reverse_blocks next* end_cb_reverse_blocks') 104 105 def render(self, context): 106 return ''.join(p.nodelist.render(context) for p in self.parse_result.parts[::-1]) 107 108 @register.tag 109 class ComplexGrammar1(TemplateTag): 110 """ 111 Some complex grammar rule. 112 """ 113 grammar = Grammar('cb_complex1 A* B C? D+ E? end_cb_complex1') 114 115 @register.tag 116 class PrintParam1(TemplateTag): 117 """ 118 Print tag parameters. 119 {% cb_print_params "param1" "param2" ... %} 120 """ 121 grammar = Grammar('cb_print_params') 122 123 def render(self, context): 124 return ' '.join(self.parse_result.arguments) 125 126 @register.tag 127 class PrintParam2(TemplateTag): 128 """ 129 Print tag parameters. 130 {% cb_print_params2 "param1" "param2" ... %} {% end_cb_print_params2 "end1" "end2" ... %} 131 """ 132 grammar = Grammar('cb_print_params2 end_cb_print_params2') 133 134 def render(self, context): 135 return ' '.join(self.parse_result.parts[0].arguments) + \ 136 self.parse_result.parts[0].nodelist.render(context) + \ 137 ' '.join(self.parse_result.parts[-1].arguments) 138 139 140 @register.tag 141 class PrintAndResolveParams(TemplateTag): 142 """ 143 Print each parameters after resolving in in the context. 144 {% cb_print_and_resolv "param1" "param2" ... %} 145 """ 146 grammar = Grammar('cb_print_and_resolv') 147 148 def __init__(self, parser, parse_result): 149 self.variables = [ 150 parser.compile_filter(a) for a in parse_result.arguments] 151 152 def render(self, context): 153 return ' '.join(map(str, (v.resolve(context) for v in self.variables))) -
tests/template_tests/test_custom.py
diff --git a/tests/template_tests/test_custom.py b/tests/template_tests/test_custom.py index 4aea082..1568b8d 100644
a b class CustomTagTests(TestCase): 355 355 "'assignment_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'", 356 356 template.Template, '{% load custom %}{% assignment_unlimited_args_kwargs 37 eggs="scrambled" eggs="scrambled" as var %}The result is: {{ var }}') 357 357 358 def test_class_based_tags(self): 359 c = template.Context({'value': 42}) 360 361 t = template.Template('{% load class_based %}a{% cb_one_tag %}b') 362 self.assertEqual(t.render(c), 'ab') 363 364 t = template.Template('{% load class_based %}a{% cb_two_tags %}b{% end_cb_two_tags %}c') 365 self.assertEqual(t.render(c), 'abc') 366 367 t = template.Template('{% load class_based %}a{% cb_to_upper %}b{% end_cb_to_upper %}c') 368 self.assertEqual(t.render(c), 'aBc') 369 370 t = template.Template('{% load class_based %}a{% cb_with_middle %}b{% middle %}c{% end_cb_with_middle %}d') 371 self.assertEqual(t.render(c), 'abcd') 372 373 t = template.Template('{% load class_based %}a{% cb_render_second %}b{% middle %}c{% end_cb_render_second %}d') 374 self.assertEqual(t.render(c), 'acd') 375 376 t = template.Template('{% load class_based %}a{% cb_render_second2 %}b{% middle %}c{% end_cb_render_second2 %}d') 377 self.assertEqual(t.render(c), 'acd') 378 379 t = template.Template('{% load class_based %}a{% cb_optional_middle %}b{% middle %}c{% end_cb_optional_middle %}d') 380 self.assertEqual(t.render(c), 'abcd') 381 382 t = template.Template('{% load class_based %}a{% cb_optional_middle %}b{% end_cb_optional_middle %}c') 383 self.assertEqual(t.render(c), 'abc') 384 385 t = template.Template('{% load class_based %}a{% cb_repeating_middle %}b{% end_cb_repeating_middle %}c') 386 self.assertEqual(t.render(c), 'abc') 387 388 t = template.Template('{% load class_based %}a{% cb_repeating_middle %}b{% middle %}c{% end_cb_repeating_middle %}d') 389 self.assertEqual(t.render(c), 'abcd') 390 391 t = template.Template('{% load class_based %}a{% cb_repeating_middle %}b{% middle %}c{% middle %}d{% end_cb_repeating_middle %}e') 392 self.assertEqual(t.render(c), 'abcde') 393 394 t = template.Template('{% load class_based %}a{% cb_repeating_middle %}b{% middle %}c{% middle %}d{% middle %}e{% end_cb_repeating_middle %}f') 395 self.assertEqual(t.render(c), 'abcdef') 396 397 t = template.Template('{% load class_based %}a{% cb_one_or_more_middle %}b{% middle %}c{% end_cb_one_or_more_middle %}d') 398 self.assertEqual(t.render(c), 'abcd') 399 400 t = template.Template('{% load class_based %}a{% cb_one_or_more_middle %}b{% middle %}c{% middle %}d{% end_cb_one_or_more_middle %}e') 401 self.assertEqual(t.render(c), 'abcde') 402 403 t = template.Template('{% load class_based %}a{% cb_one_or_more_middle %}b{% middle %}c{% middle %}d{% middle %}e{% end_cb_one_or_more_middle %}f') 404 self.assertEqual(t.render(c), 'abcdef') 405 406 t = template.Template('{% load class_based %}a{% cb_reverse_blocks %}b{% next %}c{% next %}d{% next %}e{% end_cb_reverse_blocks %}f') 407 self.assertEqual(t.render(c), 'aedcbf') 408 409 t = template.Template('{% load class_based %}_{% cb_complex1 %}_{% B %}_{% C %}_{% D %}_{% end_cb_complex1 %}_') 410 self.assertEqual(t.render(c), '______') 411 412 t = template.Template('{% load class_based %}_{% cb_complex1 %}_{% B %}_{% C %}_{% D %}_{% E %}_{% end_cb_complex1 %}_') 413 self.assertEqual(t.render(c), '_______') 414 415 t = template.Template('{% load class_based %}_{% cb_complex1 %}_{% A %}_{% A %}_{% B %}_{% C %}_{% D %}_{% E %}_{% end_cb_complex1 %}_') 416 self.assertEqual(t.render(c), '_________') 417 418 t = template.Template('{% load class_based %}_{% cb_complex1 %}_{% A %}_{% A %}_{% B %}_{% C %}_{% D %}_{% E %}_{% end_cb_complex1 %}_') 419 self.assertEqual(t.render(c), '_________') 420 421 t = template.Template('{% load class_based %}_{% cb_complex1 %}_{% A %}_{% A %}_{% B %}_{% C %}_{% D %}_{% D %}_{% E %}_{% end_cb_complex1 %}_') 422 self.assertEqual(t.render(c), '__________') 423 424 t = template.Template('{% load class_based %}{% cb_print_params %}') 425 self.assertEqual(t.render(c), '') 426 427 t = template.Template('{% load class_based %}{% cb_print_params 5 %}') 428 self.assertEqual(t.render(c), '5') 429 430 t = template.Template('{% load class_based %}{% cb_print_params value value %}') 431 self.assertEqual(t.render(c), 'value value') 432 433 t = template.Template('{% load class_based %}{% cb_print_params2 start start2 %}...{% end_cb_print_params2 end end2 %}') 434 self.assertEqual(t.render(c), 'start start2...end end2') 435 436 t = template.Template('{% load class_based %}{% cb_print_and_resolv value value 5 %}') 437 self.assertEqual(t.render(c), '42 42 5') 438 439 # Template tag exceptions 440 six.assertRaisesRegex(self, template.TemplateSyntaxError, 'Expected "middle" template tag, but found "end_cb_one_or_more_middle" instead.', 441 template.Template, '{% load class_based %}a{% cb_one_or_more_middle %}b{% end_cb_one_or_more_middle %}c') 442 443 # Test grammar 444 g = template.Grammar('start middle* end') 445 self.assertEqual(g.is_simple, False) 446 self.assertEqual(g.first_tagname, 'start') 447 self.assertEqual(g.as_string(), 'start middle* end') 448 449 g = template.Grammar('simple_tag') 450 self.assertEqual(g.is_simple, True) 451 self.assertEqual(g.first_tagname, 'simple_tag') 452 self.assertEqual(g.as_string(), 'simple_tag') 453 454 # Grammar exceptions 455 456 six.assertRaisesRegex(self, template.GrammarException, 'invalid_tag# is not a valid template tag.', 457 template.Grammar, 'invalid_tag#') 458 459 six.assertRaisesRegex(self, template.GrammarException, 'The first template tag should not repeat.', 460 template.Grammar, 'repeat_first*') 461 462 six.assertRaisesRegex(self, template.GrammarException, 'The first template tag should not repeat.', 463 template.Grammar, 'repeat_first2+ other_tag') 464 465 six.assertRaisesRegex(self, template.GrammarException, 'The last template tag should not repeat.', 466 template.Grammar, 'repeat_last last+') 467 468 six.assertRaisesRegex(self, template.GrammarException, 'The template tag middle is defined more than once.', 469 template.Grammar, 'first middle other_middle+ again_another* middle? middle last') 470 471 six.assertRaisesRegex(self, template.GrammarException, 'No template tags given.', 472 template.Grammar, '') 473 474 # Registering of template tags in a library 475 l = template.Library() 476 477 @l.tag 478 class Tag(template.TemplateTag): 479 """ Tag documentation """ 480 grammar = template.Grammar('start end') 481 482 self.assertEqual(l.tags['start'].grammar, Tag.grammar) 483 self.assertEqual(l.tags['start'].__doc__, Tag.__doc__) 484 485 class TagWithoutGrammar(template.TemplateTag): 486 pass 487 488 six.assertRaisesRegex(self, template.TemplateSyntaxError, 'TemplateTag has no valid grammar.', 489 l.tag, TagWithoutGrammar) 490 491 # Registering a tag, which is defined by a function, but without 492 # grammar should get an UnknownGrammar instance attached 493 l = template.Library() 494 def tag_without_grammar(parser, token): pass 495 l.tag(tag_without_grammar) 496 497 self.assertEqual(l.tags.get('tag_without_grammar', None), tag_without_grammar) 498 self.assertEqual(isinstance(tag_without_grammar.grammar, template.UnknownGrammar), True) 499 self.assertEqual(tag_without_grammar.grammar.first_tagname, 'tag_without_grammar') 500 501 358 502 def test_assignment_tag_registration(self): 359 503 # Test that the decorators preserve the decorated function's docstring, name and attributes. 360 504 self.verify_tag(custom.assignment_no_params, 'assignment_no_params') -
new file tests/template_tests/test_grammar.py
diff --git a/tests/template_tests/test_grammar.py b/tests/template_tests/test_grammar.py new file mode 100644 index 0000000..2e9e557
- + 1 from __future__ import absolute_import, unicode_literals 2 3 from django import template 4 from django.utils import six 5 from django.utils.unittest import TestCase 6 7 class LibraryGrammarTests(TestCase): 8 def test_library_grammar(self): 9 # Custom tags 10 library = template.get_library('custom') 11 grammar = library.get_grammar() 12 six.assertRegex(self, grammar.as_string(), 'current_app *::= current_app') 13 six.assertRegex(self, grammar.as_string(), 'inclusion_no_params *::= inclusion_no_params') 14 15 # Test class based tags 16 library = template.get_library('class_based') 17 grammar = library.get_grammar() 18 six.assertRegex(self, grammar.as_string(), r'cb_one_tag *::= cb_one_tag') 19 six.assertRegex(self, grammar.as_string(), r'cb_print_params2 *::= cb_print_params2 end_cb_print_params2') 20 six.assertRegex(self, grammar.as_string(), r'cb_complex1 *::= cb_complex1 A\* B C\? D\+ E\? end_cb_complex1')