Code

Ticket #2539: patch_2539_code_tests.diff

File patch_2539_code_tests.diff, 17.4 KB (added by durdinator, 6 years ago)

Patch against [6589], including tests and docs

Line 
1Index: django/templatetags/__init__.py
2===================================================================
3--- django/templatetags/__init__.py     (revision 6589)
4+++ django/templatetags/__init__.py     (working copy)
5@@ -1,7 +1,4 @@
6 from django.conf import settings
7 
8-for a in settings.INSTALLED_APPS:
9-    try:
10-        __path__.extend(__import__(a + '.templatetags', {}, {}, ['']).__path__)
11-    except ImportError:
12-        pass
13+# Make django.template.* tag libraries available from django.templatetags.* for consistent loading
14+__path__.extend(__import__('django.template', {}, {}, ['']).__path__)
15Index: django/contrib/admin/views/doc.py
16===================================================================
17--- django/contrib/admin/views/doc.py   (revision 6589)
18+++ django/contrib/admin/views/doc.py   (working copy)
19@@ -1,4 +1,4 @@
20-from django import template, templatetags
21+from django import template
22 from django.template import RequestContext
23 from django.conf import settings
24 from django.contrib.admin.views.decorators import staff_member_required
25@@ -40,7 +40,7 @@
26     load_all_installed_template_libraries()
27 
28     tags = []
29-    for module_name, library in template.libraries.items():
30+    for library_name, (library, app_name, module_name) in template.libraries.items():
31         for tag_name, tag_func in library.tags.items():
32             title, body, metadata = utils.parse_docstring(tag_func.__doc__)
33             if title:
34@@ -49,10 +49,10 @@
35                 body = utils.parse_rst(body, 'tag', _('tag:') + tag_name)
36             for key in metadata:
37                 metadata[key] = utils.parse_rst(metadata[key], 'tag', _('tag:') + tag_name)
38-            if library in template.builtins:
39+            if library in zip(*template.builtins)[0]:
40                 tag_library = None
41             else:
42-                tag_library = module_name.split('.')[-1]
43+                tag_library = module_name, library_name
44             tags.append({
45                 'name': tag_name,
46                 'title': title,
47@@ -71,7 +71,7 @@
48     load_all_installed_template_libraries()
49 
50     filters = []
51-    for module_name, library in template.libraries.items():
52+    for library_name, (library, app_name, module_name) in template.libraries.items():
53         for filter_name, filter_func in library.filters.items():
54             title, body, metadata = utils.parse_docstring(filter_func.__doc__)
55             if title:
56@@ -80,10 +80,10 @@
57                 body = utils.parse_rst(body, 'filter', _('filter:') + filter_name)
58             for key in metadata:
59                 metadata[key] = utils.parse_rst(metadata[key], 'filter', _('filter:') + filter_name)
60-            if library in template.builtins:
61+            if library in zip(*template.builtins)[0]:
62                 tag_library = None
63             else:
64-                tag_library = module_name.split('.')[-1]
65+                tag_library = module_name, library_name
66             filters.append({
67                 'name': filter_name,
68                 'title': title,
69@@ -267,11 +267,20 @@
70 
71 def load_all_installed_template_libraries():
72     # Load/register all template tag libraries from installed apps.
73-    for e in templatetags.__path__:
74-        libraries = [os.path.splitext(p)[0] for p in os.listdir(e) if p.endswith('.py') and p[0].isalpha()]
75+    for a in settings.INSTALLED_APPS:
76+        try:
77+            path = __import__(a + '.templatetags', {}, {}, ['']).__path__[0]
78+        except ImportError:
79+            continue
80+        libraries = []
81+
82+        for dirpath, dirnames, filenames in os.walk(path):
83+            libraries.extend([os.path.splitext(p)[0] for p in filenames if p.endswith('.py') and p[0].isalpha()])
84+            libraries.extend([p for p in dirnames if '.' not in p and os.path.exists(os.path.join(p, '__init__.py')) and p[0].isalpha()])
85+
86         for library_name in libraries:
87             try:
88-                lib = template.get_library("django.templatetags.%s" % library_name.split('.')[-1])
89+                lib = template.get_library("%s.%s" % (a, library_name))
90             except template.InvalidTemplateLibrary:
91                 pass
92 
93Index: django/contrib/admin/templates/admin_doc/template_tag_index.html
94===================================================================
95--- django/contrib/admin/templates/admin_doc/template_tag_index.html    (revision 6589)
96+++ django/contrib/admin/templates/admin_doc/template_tag_index.html    (working copy)
97@@ -12,8 +12,8 @@
98 {% regroup tags|dictsort:"library" by library as tag_libraries %}
99 {% for library in tag_libraries %}
100 <div class="module">
101-    <h2>{% if library.grouper %}{{ library.grouper }}{% else %}Built-in tags{% endif %}</h2>
102-    {% if library.grouper %}<p class="small quiet">To use these tags, put <code>{% templatetag openblock %} load {{ library.grouper }} {% templatetag closeblock %}</code> in your template before using the tag.</p><hr>{% endif %}
103+    <h2>{% if library.grouper %}{{ library.grouper.0 }} ({{ library.grouper.1 }}){% else %}Built-in tags{% endif %}</h2>
104+    {% if library.grouper %}<p class="small quiet">To use these tags, put <code>{% templatetag openblock %} load {{ library.grouper.0 }} {% templatetag closeblock %}</code> in your template before using the tag.</p><hr>{% endif %}
105     {% for tag in library.list|dictsort:"name" %}
106     <h3 id="{{ tag.name }}">{{ tag.name }}</h3>
107     <h4>{{ tag.title }}</h4>
108@@ -33,7 +33,7 @@
109 {% regroup tags|dictsort:"library" by library as tag_libraries %}
110 {% for library in tag_libraries %}
111 <div class="module">
112-    <h2>{% if library.grouper %}{{ library.grouper }}{% else %}Built-in tags{% endif %}</h2>
113+    <h2>{% if library.grouper %}{{ library.grouper.0 }}{% else %}Built-in tags{% endif %}</h2>
114     <ul>
115     {% for tag in library.list|dictsort:"name" %}
116         <li><a href="#{{ tag.name }}">{{ tag.name }}</a></li>
117Index: django/contrib/admin/templates/admin_doc/template_filter_index.html
118===================================================================
119--- django/contrib/admin/templates/admin_doc/template_filter_index.html (revision 6589)
120+++ django/contrib/admin/templates/admin_doc/template_filter_index.html (working copy)
121@@ -12,8 +12,8 @@
122 {% regroup filters|dictsort:"library" by library as filter_libraries %}
123 {% for library in filter_libraries %}
124 <div class="module">
125-    <h2>{% if library.grouper %}{{ library.grouper }}{% else %}Built-in filters{% endif %}</h2>
126-    {% if library.grouper %}<p class="small quiet">To use these filters, put <code>{% templatetag openblock %} load {{ library.grouper }} {% templatetag closeblock %}</code> in your template before using the filter.</p><hr>{% endif %}
127+    <h2>{% if library.grouper %}{{ library.grouper.0 }} ({{ library.grouper.1 }}){% else %}Built-in filters{% endif %}</h2>
128+    {% if library.grouper %}<p class="small quiet">To use these filters, put <code>{% templatetag openblock %} load {{ library.grouper.0 }} {% templatetag closeblock %}</code> in your template before using the filter.</p><hr>{% endif %}
129     {% for filter in library.list|dictsort:"name" %}
130     <h3 id="{{ filter.name }}">{{ filter.name }}</h3>
131     <p>{{ filter.title }}</p>
132@@ -33,7 +33,7 @@
133 {% regroup filters|dictsort:"library" by library as filter_libraries %}
134 {% for library in filter_libraries %}
135 <div class="module">
136-    <h2>{% if library.grouper %}{{ library.grouper }}{% else %}Built-in filters{% endif %}</h2>
137+    <h2>{% if library.grouper %}{{ library.grouper.0 }}{% else %}Built-in filters{% endif %}</h2>
138     <ul>
139     {% for filter in library.list|dictsort:"name" %}
140         <li><a href="#{{ filter.name }}">{{ filter.name }}</a></li>
141Index: django/contrib/markup/tests.py
142===================================================================
143--- django/contrib/markup/tests.py      (revision 6589)
144+++ django/contrib/markup/tests.py      (working copy)
145@@ -4,7 +4,7 @@
146 import re
147 import unittest
148 
149-add_to_builtins('django.contrib.markup.templatetags.markup')
150+add_to_builtins('django.contrib.markup', 'markup')
151 
152 class Templates(unittest.TestCase):
153     def test_textile(self):
154Index: django/template/__init__.py
155===================================================================
156--- django/template/__init__.py (revision 6589)
157+++ django/template/__init__.py (working copy)
158@@ -253,8 +253,9 @@
159         self.tokens = tokens
160         self.tags = {}
161         self.filters = {}
162-        for lib in builtins:
163-            self.add_library(lib)
164+        for lib, app, ref in builtins:
165+            self.add_library(lib, ref)
166+            self.add_library(lib, app + '.' + ref)
167 
168     def parse(self, parse_until=None):
169         if parse_until is None: parse_until = []
170@@ -344,9 +345,13 @@
171     def delete_first_token(self):
172         del self.tokens[0]
173 
174-    def add_library(self, lib):
175+    def add_library(self, lib, ref):
176+        def with_ref(d):
177+            return dict([(ref + '.' + k, v) for (k, v) in d.items()])
178         self.tags.update(lib.tags)
179+        self.tags.update(with_ref(lib.tags))
180         self.filters.update(lib.filters)
181+        self.filters.update(with_ref(lib.filters))
182 
183     def compile_filter(self, token):
184         "Convenient wrapper for FilterExpression"
185@@ -495,7 +500,7 @@
186 ^"(?P<constant>%(str)s)"|
187 ^(?P<var>[%(var_chars)s]+)|
188  (?:%(filter_sep)s
189-     (?P<filter_name>\w+)
190+     (?P<filter_name>[\w\.]+)
191          (?:%(arg_sep)s
192              (?:
193               %(i18n_open)s"(?P<i18n_arg>%(str)s)"%(i18n_close)s|
194@@ -969,22 +974,49 @@
195             return func
196         return dec
197 
198-def get_library(module_name):
199-    lib = libraries.get(module_name, None)
200-    if not lib:
201+def get_library(library_name):
202+    return get_library_details(library_name)[0]
203+
204+def get_library_details(library_name):
205+    library_details = libraries.get(library_name)
206+    if not library_details:
207+        mod = None
208+        class ModFound(Exception): pass
209+        tried = []
210+        apps = ['django'] + settings.INSTALLED_APPS
211         try:
212-            mod = __import__(module_name, {}, {}, [''])
213-        except ImportError, e:
214-            raise InvalidTemplateLibrary, "Could not load template library from %s, %s" % (module_name, e)
215-        try:
216-            lib = mod.register
217-            libraries[module_name] = lib
218-        except AttributeError:
219-            raise InvalidTemplateLibrary, "Template library %s does not have a variable named 'register'" % module_name
220-    return lib
221+            # For library_name of the form <app.path>.<library.path>
222+            for app_name in apps:
223+                if library_name.startswith(app_name + '.'):
224+                    module_name = library_name[len(app_name) + 1:]
225+                    try:
226+                        mod = __import__(app_name + '.templatetags.' + module_name, {}, {}, [''])
227+                    except ImportError, e:
228+                        tried.append((module_name, app_name))
229+                    else:
230+                        raise ModFound, (mod, app_name, module_name)
231+            # For library_name of the form <library.path>
232+            for app_name in apps:
233+                try:
234+                    mod = __import__(app_name + '.templatetags.' + library_name, {}, {}, [''])
235+                except ImportError, e:
236+                    tried.append((library_name, app_name))
237+                else:
238+                    raise ModFound, (mod, app_name, library_name)
239+        except ModFound, e:
240+            mod, app_name, module_name = e.args
241+            try:
242+                lib = mod.register
243+                library_details = libraries[app_name + '.' + module_name] = (lib, app_name, module_name)
244+            except AttributeError:
245+                raise InvalidTemplateLibrary, "Template library %s does not have a variable named 'register'" % library_name
246+        else:
247+            tried_s = ", ".join(["'%s' from app '%s'" % t for t in tried])
248+            raise InvalidTemplateLibrary, "Could not load template library %s; tried %s" % (library_name, tried_s)
249+    return library_details
250 
251-def add_to_builtins(module_name):
252-    builtins.append(get_library(module_name))
253+def add_to_builtins(app_name, library_name):
254+    builtins.append((get_library(app_name + '.' + library_name), app_name, library_name))
255 
256-add_to_builtins('django.template.defaulttags')
257-add_to_builtins('django.template.defaultfilters')
258+add_to_builtins('django', 'defaulttags')
259+add_to_builtins('django', 'defaultfilters')
260Index: django/template/defaulttags.py
261===================================================================
262--- django/template/defaulttags.py      (revision 6589)
263+++ django/template/defaulttags.py      (working copy)
264@@ -2,7 +2,7 @@
265 
266 from django.template import Node, NodeList, Template, Context, Variable
267 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
268-from django.template import get_library, Library, InvalidTemplateLibrary
269+from django.template import get_library_details, Library, InvalidTemplateLibrary
270 from django.conf import settings
271 from django.utils.encoding import smart_str, smart_unicode
272 from django.utils.itercompat import groupby
273@@ -801,8 +801,8 @@
274     for taglib in bits[1:]:
275         # add the library to the parser
276         try:
277-            lib = get_library("django.templatetags.%s" % taglib)
278-            parser.add_library(lib)
279+            lib, app_name, ref = get_library_details(taglib)
280+            parser.add_library(lib, taglib)
281         except InvalidTemplateLibrary, e:
282             raise TemplateSyntaxError, "'%s' is not a valid tag library: %s" % (taglib, e)
283     return LoadNode()
284Index: django/template/loader.py
285===================================================================
286--- django/template/loader.py   (revision 6589)
287+++ django/template/loader.py   (working copy)
288@@ -115,4 +115,4 @@
289     # If we get here, none of the templates could be loaded
290     raise TemplateDoesNotExist, ', '.join(template_name_list)
291 
292-add_to_builtins('django.template.loader_tags')
293+add_to_builtins('django', 'loader_tags')
294Index: tests/regressiontests/humanize/tests.py
295===================================================================
296--- tests/regressiontests/humanize/tests.py     (revision 6589)
297+++ tests/regressiontests/humanize/tests.py     (working copy)
298@@ -4,7 +4,7 @@
299 from django.utils.dateformat import DateFormat
300 from django.utils.translation import ugettext as _
301 
302-add_to_builtins('django.contrib.humanize.templatetags.humanize')
303+add_to_builtins('django.contrib.humanize', 'humanize')
304 
305 class HumanizeTests(unittest.TestCase):
306 
307Index: tests/regressiontests/templates/tests.py
308===================================================================
309--- tests/regressiontests/templates/tests.py    (revision 6589)
310+++ tests/regressiontests/templates/tests.py    (working copy)
311@@ -41,7 +41,7 @@
312 
313 register.tag("echo", do_echo)
314 
315-template.libraries['django.templatetags.testtags'] = register
316+template.libraries['testtags'] = (register, 'django', 'testtags')
317 
318 #####################################
319 # Helper objects for template tests #
320@@ -819,6 +819,20 @@
321             'cache08' : ('{% load cache %}{% cache %}{% endcache %}', {}, template.TemplateSyntaxError),
322             'cache09' : ('{% load cache %}{% cache 1 %}{% endcache %}', {}, template.TemplateSyntaxError),
323             'cache10' : ('{% load cache %}{% cache foo bar %}{% endcache %}', {}, template.TemplateSyntaxError),
324+
325+            ### TAG LIBRARY NAMESPACING ###############################################
326+            'library-namespacing01': ("{% load defaulttags %}", {}, ""),
327+            'library-namespacing02': ("{% load django.defaulttags %}", {}, ""),
328+
329+            ### TAG NAMESPACING #######################################################
330+            'tag-namespacing01': ('{% defaulttags.templatetag openbrace %}', {}, '{'),
331+            'tag-namespacing02': ('{% django.defaulttags.templatetag openbrace %}', {}, '{'),
332+
333+            'tag-namespacing03': ('{% defaulttags.with a as b %}{{ b }}{% endwith %}', {'a': 'a'}, 'a'),
334+            'tag-namespacing04': ('{% django.defaulttags.with a as b %}{{ b }}{% endwith %}', {'a': 'a'}, 'a'),
335+
336+            'tag-namespacing05': ('{{ a|defaultfilters.upper }}', {'a': 'a'}, 'A'),
337+            'tag-namespacing06': ('{{ a|django.defaultfilters.upper }}', {'a': 'a'}, 'A'),
338         }
339 
340         # Register our custom template loader.
341Index: docs/templates.txt
342===================================================================
343--- docs/templates.txt  (revision 6589)
344+++ docs/templates.txt  (working copy)
345@@ -363,6 +363,29 @@
346 
347 This is a feature for the sake of maintainability and sanity.
348 
349+Custom libraries and name conflicts
350+-----------------------------------
351+
352+If two or more libraries in different apps share the same name, then you will
353+have to load them by giving the app name and the library name, separated by
354+a dot (``.``)::
355+
356+    {% load someapp.taglibrary %}
357+    {% load otherapp.taglibrary %}
358+
359+In a similar fashion, if two different libraries offer tags or filters with
360+the same name, you can prefix the use of the tag or filter with the library
361+name to make it clear which tag or filter you wish to use::
362+
363+    {% i18n.trans "this is a test" %}
364+
365+If there is a conflict in both the library names and the tag or filter names,
366+the tag or filter prefix must be the name you loaded the library with:
367+
368+    {% load someapp.taglibrary otherapp.taglibrary %}
369+    {% someapp.taglibrary.sometag "This is the tag in someapp" %}
370+    {% otherapp.taglibrary.sometag "A completely different tag in otherapp" %}
371+
372 Built-in tag and filter reference
373 =================================
374