Code

Ticket #3544: 3544.contexttemplatecache.3.2.diff

File 3544.contexttemplatecache.3.2.diff, 19.4 KB (added by emulbreh, 6 years ago)

typo ..

Line 
1Index: django/template/__init__.py
2===================================================================
3--- django/template/__init__.py (revision 7698)
4+++ django/template/__init__.py (working copy)
5@@ -196,8 +196,8 @@
6             ({TOKEN_TEXT: 'Text', TOKEN_VAR: 'Var', TOKEN_BLOCK: 'Block', TOKEN_COMMENT: 'Comment'}[self.token_type],
7             self.contents[:20].replace('\n', ''))
8 
9-    def split_contents(self):
10-        return list(smart_split(self.contents))
11+    def split_contents(self, separator_chars=None):
12+        return list(smart_split(self.contents, separator_chars=separator_chars))
13 
14 class Lexer(object):
15     def __init__(self, template_string, origin):
16@@ -898,11 +898,10 @@
17                     dict = func(*args)
18 
19                     if not getattr(self, 'nodelist', False):
20-                        from django.template.loader import get_template, select_template
21                         if not isinstance(file_name, basestring) and is_iterable(file_name):
22-                            t = select_template(file_name)
23+                            t = context.select_template(file_name)
24                         else:
25-                            t = get_template(file_name)
26+                            t = context.get_template(file_name)
27                         self.nodelist = t.nodelist
28                     return self.nodelist.render(context_class(dict,
29                             autoescape=context.autoescape))
30@@ -913,6 +912,43 @@
31             return func
32         return dec
33 
34+def parse_context_map(bits, separators=('and', ',')):
35+    if len(bits) % 4 != 3:
36+        raise ValueError
37+    context_map = {}
38+    bits = [separators[0]] + bits
39+    for i in range(len(bits) / 4):
40+        separator, value, as_, name = bits[4*i:4*(i+1)]
41+        if not separator in separators or as_ != 'as':
42+            raise ValueError
43+        context_map[name] = Variable(value)
44+    return context_map
45+
46+def parse_string_literal(bit, allow_empty=True):   
47+    if len(bit) < 2 or not allow_empty and len(bit) < 3:
48+        raise ValueError
49+    q = bit[0]
50+    if not q in ('"',"'") or bit[-1] != q:
51+        raise ValueError
52+    return bit[1:-1]
53+
54+def render_with_context_map(renderable, context_map, context):
55+    """
56+    renderable: a NodeList or Template
57+    context_map: a dict mapping strings to Variables
58+    context: a context object
59+   
60+    """
61+    if context_map is None:
62+        return renderable.render(context)
63+    dict = {}
64+    for name in context_map:
65+        dict[name] = context_map[name].resolve(context)
66+    context.update(dict)
67+    output = renderable.render(context)
68+    context.pop()
69+    return output
70+
71 def get_library(module_name):
72     lib = libraries.get(module_name, None)
73     if not lib:
74Index: django/template/defaulttags.py
75===================================================================
76--- django/template/defaulttags.py      (revision 7698)
77+++ django/template/defaulttags.py      (working copy)
78@@ -11,6 +11,7 @@
79 from django.template import Node, NodeList, Template, Context, Variable
80 from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
81 from django.template import get_library, Library, InvalidTemplateLibrary
82+from django.template import render_with_context_map, parse_context_map
83 from django.conf import settings
84 from django.utils.encoding import smart_str, smart_unicode
85 from django.utils.itercompat import groupby
86@@ -390,21 +391,15 @@
87         return str(int(round(ratio)))
88 
89 class WithNode(Node):
90-    def __init__(self, var, name, nodelist):
91-        self.var = var
92-        self.name = name
93+    def __init__(self, context_map, nodelist):
94+        self.context_map = context_map
95         self.nodelist = nodelist
96 
97     def __repr__(self):
98         return "<WithNode>"
99 
100     def render(self, context):
101-        val = self.var.resolve(context)
102-        context.push()
103-        context[self.name] = val
104-        output = self.nodelist.render(context)
105-        context.pop()
106-        return output
107+        return render_with_context_map(self.nodelist, self.context_map, context)
108 
109 #@register.tag
110 def autoescape(parser, token):
111@@ -1082,7 +1077,7 @@
112 #@register.tag
113 def do_with(parser, token):
114     """
115-    Adds a value to the context (inside of this block) for caching and easy
116+    Adds values to the context (inside of this block) for caching and easy
117     access.
118 
119     For example::
120@@ -1090,14 +1085,19 @@
121         {% with person.some_sql_method as total %}
122             {{ total }} object{{ total|pluralize }}
123         {% endwith %}
124+       
125+        {% with person.some_sql_method as total, person.get_full_name as full_name %}
126+            {{ full_name }}: {{ total }} object{{ total|pluralize }}
127+        {% endwith %}
128+       
129     """
130-    bits = list(token.split_contents())
131-    if len(bits) != 4 or bits[2] != "as":
132-        raise TemplateSyntaxError("%r expected format is 'value as name'" %
133+    bits = list(token.split_contents(separator_chars=','))
134+    try:
135+        context_map = parse_context_map(bits[1:])
136+    except ValueError:
137+        raise TemplateSyntaxError("%r expected format is 'value as name (and value as name)*'" %
138                                   bits[0])
139-    var = parser.compile_filter(bits[1])
140-    name = bits[3]
141     nodelist = parser.parse(('endwith',))
142     parser.delete_first_token()
143-    return WithNode(var, name, nodelist)
144+    return WithNode(context_map, nodelist)
145 do_with = register.tag('with', do_with)
146Index: django/template/context.py
147===================================================================
148--- django/template/context.py  (revision 7698)
149+++ django/template/context.py  (working copy)
150@@ -9,10 +9,12 @@
151 
152 class Context(object):
153     "A stack container for variable context"
154-    def __init__(self, dict_=None, autoescape=True):
155+    def __init__(self, dict_=None, autoescape=True, loader=None):
156         dict_ = dict_ or {}
157         self.dicts = [dict_]
158         self.autoescape = autoescape
159+        self.template_cache = {}
160+        self.loader = loader
161 
162     def __repr__(self):
163         return repr(self.dicts)
164@@ -65,6 +67,25 @@
165         self.dicts = [other_dict] + self.dicts
166         return other_dict
167 
168+    def get_template(self, template_name):
169+        if not template_name in self.template_cache:
170+            if self.loader is None:
171+                from django.template import loader
172+                tpl = loader.get_template(template_name)
173+            else:
174+                tpl = self.loader.get_template(template_name)
175+            self.template_cache[template_name] = tpl
176+        return self.template_cache[template_name]
177+
178+    def select_template(self, template_name_list):
179+        from django.template import TemplateDoesNotExist
180+        for template_name in template_name_list:
181+            try:
182+                return self.get_template(template_name)
183+            except TemplateDoesNotExist:
184+                continue
185+        raise TemplateDoesNotExist, ', '.join(template_name_list)
186+
187 # This is a function rather than module-level procedural code because we only
188 # want it to execute if somebody uses RequestContext.
189 def get_standard_processors():
190@@ -93,8 +114,8 @@
191     Additional processors can be specified as a list of callables
192     using the "processors" keyword argument.
193     """
194-    def __init__(self, request, dict=None, processors=None):
195-        Context.__init__(self, dict)
196+    def __init__(self, request, dict=None, processors=None, loader=None):
197+        Context.__init__(self, dict, loader=loader)
198         if processors is None:
199             processors = ()
200         else:
201Index: django/template/loader_tags.py
202===================================================================
203--- django/template/loader_tags.py      (revision 7698)
204+++ django/template/loader_tags.py      (working copy)
205@@ -1,5 +1,6 @@
206 from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
207 from django.template import Library, Node, TextNode
208+from django.template import render_with_context_map, parse_context_map, parse_string_literal
209 from django.template.loader import get_template, get_template_from_string, find_template_source
210 from django.conf import settings
211 from django.utils.safestring import mark_safe
212@@ -39,10 +40,9 @@
213 class ExtendsNode(Node):
214     must_be_first = True
215 
216-    def __init__(self, nodelist, parent_name, parent_name_expr, template_dirs=None):
217+    def __init__(self, nodelist, parent_name, parent_name_expr):
218         self.nodelist = nodelist
219         self.parent_name, self.parent_name_expr = parent_name, parent_name_expr
220-        self.template_dirs = template_dirs
221 
222     def __repr__(self):
223         if self.parent_name_expr:
224@@ -61,11 +61,9 @@
225         if hasattr(parent, 'render'):
226             return parent # parent is a Template object
227         try:
228-            source, origin = find_template_source(parent, self.template_dirs)
229+            return context.get_template(parent)
230         except TemplateDoesNotExist:
231             raise TemplateSyntaxError, "Template %r cannot be extended, because it doesn't exist" % parent
232-        else:
233-            return get_template_from_string(source, origin, parent)
234 
235     def render(self, context):
236         compiled_parent = self.get_parent(context)
237@@ -96,31 +94,16 @@
238                 parent_block.nodelist = block_node.nodelist
239         return compiled_parent.render(context)
240 
241-class ConstantIncludeNode(Node):
242-    def __init__(self, template_path):
243-        try:
244-            t = get_template(template_path)
245-            self.template = t
246-        except:
247-            if settings.TEMPLATE_DEBUG:
248-                raise
249-            self.template = None
250-
251-    def render(self, context):
252-        if self.template:
253-            return self.template.render(context)
254-        else:
255-            return ''
256-
257 class IncludeNode(Node):
258-    def __init__(self, template_name):
259+    def __init__(self, template_name, context_map=None):
260         self.template_name = Variable(template_name)
261+        self.context_map = context_map
262 
263     def render(self, context):
264         try:
265             template_name = self.template_name.resolve(context)
266-            t = get_template(template_name)
267-            return t.render(context)
268+            tpl = context.get_template(template_name)           
269+            return render_with_context_map(tpl, self.context_map, context)
270         except TemplateSyntaxError, e:
271             if settings.TEMPLATE_DEBUG:
272                 raise
273@@ -162,9 +145,9 @@
274     if len(bits) != 2:
275         raise TemplateSyntaxError, "'%s' takes one argument" % bits[0]
276     parent_name, parent_name_expr = None, None
277-    if bits[1][0] in ('"', "'") and bits[1][-1] == bits[1][0]:
278-        parent_name = bits[1][1:-1]
279-    else:
280+    try:
281+        parent_name = parse_string_literal(bits[1])
282+    except ValueError:
283         parent_name_expr = parser.compile_filter(bits[1])
284     nodelist = parser.parse()
285     if nodelist.get_nodes_by_type(ExtendsNode):
286@@ -174,18 +157,26 @@
287 def do_include(parser, token):
288     """
289     Loads a template and renders it with the current context.
290+    Optionally takes a "with value as name (, value as name)*" clause.
291 
292     Example::
293 
294         {% include "foo/some_include" %}
295+        {% include "foo" with value as name %}
296+        {% include "foo" with value as name, bar as baz %}
297     """
298-    bits = token.contents.split()
299-    if len(bits) != 2:
300-        raise TemplateSyntaxError, "%r tag takes one argument: the name of the template to be included" % bits[0]
301-    path = bits[1]
302-    if path[0] in ('"', "'") and path[-1] == path[0]:
303-        return ConstantIncludeNode(path[1:-1])
304-    return IncludeNode(bits[1])
305+    bits = token.split_contents(separator_chars=',')
306+    syntax_error = False
307+    if len(bits) == 2:
308+        return IncludeNode(bits[1])
309+    if len(bits) > 2 and bits[2] == 'with':
310+        try:
311+            context_map = parse_context_map(bits[3:])           
312+            return IncludeNode(bits[1], context_map)
313+        except ValueError:
314+            raise ValueError, bits[3:]
315+    raise TemplateSyntaxError, "%r tag takes the name of the template to be included and an optional 'with value as name' clause." % bits[0]   
316+   
317 
318 register.tag('block', do_block)
319 register.tag('extends', do_extends)
320Index: django/utils/text.py
321===================================================================
322--- django/utils/text.py        (revision 7698)
323+++ django/utils/text.py        (working copy)
324@@ -1,7 +1,7 @@
325 import re
326 from django.conf import settings
327 from django.utils.encoding import force_unicode
328-from django.utils.functional import allow_lazy
329+from django.utils.functional import allow_lazy, memoize
330 from django.utils.translation import ugettext_lazy
331 
332 # Capitalizes the first letter of a string.
333@@ -196,23 +196,36 @@
334     return str(ustring_re.sub(fix, s))
335 javascript_quote = allow_lazy(javascript_quote, unicode)
336 
337-smart_split_re = re.compile('("(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'|[^\\s]+)')
338-def smart_split(text):
339+quoted_string_pattern = '"(?:[^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'(?:[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\''
340+def get_smart_split_regexp(chars):
341+    if chars is None:
342+        return re.compile('(%s|[^\\s]+)' % quoted_string_pattern)
343+    return re.compile('(%s|[^\\s%s]+|[%s])' % (quoted_string_pattern, chars, chars))
344+get_smart_split_regexp = memoize(get_smart_split_regexp, {}, 1)
345+
346+def smart_split(text, separator_chars=None):
347     r"""
348     Generator that splits a string by spaces, leaving quoted phrases together.
349     Supports both single and double quotes, and supports escaping quotes with
350     backslashes. In the output, strings will keep their initial and trailing
351     quote marks.
352-
353+    A `separator_chars` string can be given to additionally split by each
354+    contained character. Matched characters will be included in the output.
355+   
356     >>> list(smart_split(r'This is "a person\'s" test.'))
357     [u'This', u'is', u'"a person\\\'s"', u'test.']
358        >>> list(smart_split(r"Another 'person\'s' test."))
359        [u'Another', u"'person's'", u'test.']
360        >>> list(smart_split(r'A "\"funky\" style" test.'))
361        [u'A', u'""funky" style"', u'test.']
362+    >>> list(smart_split('a, b,c ,d, e ,f', separator_chars=','))
363+    [u'a', u',', u'b', u',', u'c', u',', u'd', u',', u'e', u',', u'f']
364+    >>> list(smart_split('a=1, b=2 , c = 3', separator_chars=',='))
365+    [u'a', u'=', u'1', u',', u'b', u'=', u'2', u',', u'c', u'=', u'3']         
366     """
367     text = force_unicode(text)
368-    for bit in smart_split_re.finditer(text):
369+    regexp = get_smart_split_regexp(separator_chars)
370+    for bit in regexp.finditer(text):
371         bit = bit.group(0)
372         if bit[0] == '"' and bit[-1] == '"':
373             yield '"' + bit[1:-1].replace('\\"', '"').replace('\\\\', '\\') + '"'
374Index: tests/regressiontests/templates/tests.py
375===================================================================
376--- tests/regressiontests/templates/tests.py    (revision 7698)
377+++ tests/regressiontests/templates/tests.py    (working copy)
378@@ -611,6 +611,26 @@
379             'include02': ('{% include "basic-syntax02" %}', {'headline': 'Included'}, "Included"),
380             'include03': ('{% include template_name %}', {'template_name': 'basic-syntax02', 'headline': 'Included'}, "Included"),
381             'include04': ('a{% include "nonexistent" %}b', {}, "ab"),
382+            'include-with01': ('{% include "basic-syntax02" with foo as headline %}', {'foo': 'Included'}, "Included"),
383+            'include-with02': ('{% include "basic-syntax03" with foo as first, bar as second %}', {"foo" : 1, "bar" : 2}, "1 --- 2"),
384+            'recursive-include': ('{% for item in items %}{{ item.label }}{% if not item.children|length_is:0 %}{% with item.children as items %}({% include "recursive-include" %}){% endwith %}{% endif %}{% endfor %}', {
385+                'items': [
386+                    {'label': 1, 'children': [
387+                        {'label': 2, 'children': [
388+                            {'label': 3, 'children': []},
389+                            {'label': 4, 'children': []},
390+                        ]},
391+                        {'label': 5, 'children': [
392+                             {'label': 6, 'children': [
393+                                 {'label': 7, 'children': [
394+                                     {'label': 8, 'children': []},     
395+                                 ]},
396+                                {'label': 9, 'children': []}, 
397+                             ]},
398+                        ]},
399+                    ]},
400+                ],
401+            }, '1(2(34)5(6(7(8)9)))'),
402 
403             ### NAMED ENDBLOCKS #######################################################
404 
405@@ -854,10 +874,20 @@
406             ### WITH TAG ########################################################
407             'with01': ('{% with dict.key as key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, '50'),
408             'with02': ('{{ key }}{% with dict.key as key %}{{ key }}-{{ dict.key }}-{{ key }}{% endwith %}{{ key }}', {'dict': {'key':50}}, ('50-50-50', 'INVALID50-50-50INVALID')),
409+            'with03': ('{% with a as b and b as a %}{{ a }}{{ b }}{% endwith %}', {'a': 'A', 'b': 'B'}, 'BA'),
410+            'with04': ('{% with a as b, b as a %}{{ a }}{{ b }}{% endwith %}', {'a': 'A', 'b': 'B'}, 'BA'),
411+            'with05': ('{% with a as b, b as a, "," as s %}{{ a }}{{ s }}{{ b }}{% endwith %}', {'a': 'A', 'b': 'B'}, 'B,A'),
412+            'with06': ('{% with a as b and \',\' as s, b as a %}{{ a }}{{ s }}{{ b }}{% endwith %}', {'a': 'A', 'b': 'B'}, 'B,A'),
413 
414             'with-error01': ('{% with dict.key xx key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError),
415             'with-error02': ('{% with dict.key as %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError),
416-
417+            'with-error03': ('{% with a as x, as y %}x{% endwith %}', {'a': 'A', 'b': 'B'}, template.TemplateSyntaxError),
418+            'with-error04': ('{% with a as x, b as %}x{% endwith %}', {'a': 'A', 'b': 'B'}, template.TemplateSyntaxError),
419+            'with-error05': ('{% with as x, b as y %}x{% endwith %}', {'a': 'A', 'b': 'B'}, template.TemplateSyntaxError),                       
420+            'with-error06': ('{% with a as x | b as y %}x{% endwith %}', {'a': 'A', 'b': 'B'}, template.TemplateSyntaxError),
421+            'with-error07': ('{% with a as x xxx b as y %}x{% endwith %}', {'a': 'A', 'b': 'B'}, template.TemplateSyntaxError),
422+            'with-error08': ('{% with a xx x, b xx y %}x{% endwith %}', {'a': 'A', 'b': 'B'}, template.TemplateSyntaxError),
423+                       
424             ### NOW TAG ########################################################
425             # Simple case
426             'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)),
427Index: tests/regressiontests/text/tests.py
428===================================================================
429--- tests/regressiontests/text/tests.py (revision 7698)
430+++ tests/regressiontests/text/tests.py (working copy)
431@@ -15,6 +15,10 @@
432 [u'"a', u"'one"]
433 >>> print list(smart_split(r'''all friends' tests'''))[1]
434 friends'
435+>>> list(smart_split('a, b,c ,d, e ,f', separator_chars=','))
436+[u'a', u',', u'b', u',', u'c', u',', u'd', u',', u'e', u',', u'f']
437+>>> list(smart_split('a=1, b=2 , c = 3', separator_chars=',='))
438+[u'a', u'=', u'1', u',', u'b', u'=', u'2', u',', u'c', u'=', u'3']
439 
440 ### urlquote #############################################################
441 >>> from django.utils.http import urlquote, urlquote_plus