From 7eefdbf7ab9f5bafe5baae2b877d93efc90e3044 Mon Sep 17 00:00:00 2001
From: Aymeric Augustin <aymeric.augustin@m4x.org>
Date: Mon, 10 Nov 2014 21:40:26 +0100
Subject: [PATCH] Cleaned up the django.template namespace.

Since this package is going to hold both the implementation of the Django
Template Language and the infrastructure for Multiple Template Engines,
it should be untied from the DTL as much as possible within our
backwards-compatibility policy.

Only public APIs (i.e. APIs mentioned in the documentation) were left.
---
 django/contrib/admindocs/views.py      | 21 +++----
 django/template/__init__.py            | 83 ++++----------------------
 django/template/base.py                | 51 ++++++++++++++++
 django/templatetags/i18n.py            |  5 +-
 django/utils/translation/trans_real.py |  4 +-
 docs/releases/1.8.txt                  | 10 +++-
 tests/template_tests/test_nodelist.py  |  3 +-
 tests/template_tests/test_parser.py    |  4 +-
 tests/template_tests/test_unicode.py   |  3 +-
 tests/template_tests/tests.py          |  5 +-
 tests/template_tests/utils.py          | 10 ++--
 11 files changed, 99 insertions(+), 100 deletions(-)

diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py
index bf706d0307..2c77a24f63 100644
--- a/django/contrib/admindocs/views.py
+++ b/django/contrib/admindocs/views.py
@@ -3,7 +3,6 @@ import inspect
 import os
 import re
 
-from django import template
 from django.apps import apps
 from django.conf import settings
 from django.contrib import admin
@@ -13,6 +12,8 @@ from django.core.exceptions import ViewDoesNotExist
 from django.http import Http404
 from django.core import urlresolvers
 from django.contrib.admindocs import utils
+from django.template.base import (builtins, get_library,
+    get_templatetags_modules, InvalidTemplateLibrary, libraries)
 from django.template.engine import Engine
 from django.utils.decorators import method_decorator
 from django.utils._os import upath
@@ -61,8 +62,8 @@ class TemplateTagIndexView(BaseAdminDocsView):
         load_all_installed_template_libraries()
 
         tags = []
-        app_libs = list(six.iteritems(template.libraries))
-        builtin_libs = [(None, lib) for lib in template.builtins]
+        app_libs = list(six.iteritems(libraries))
+        builtin_libs = [(None, lib) for lib in builtins]
         for module_name, library in builtin_libs + app_libs:
             for tag_name, tag_func in library.tags.items():
                 title, body, metadata = utils.parse_docstring(tag_func.__doc__)
@@ -72,7 +73,7 @@ class TemplateTagIndexView(BaseAdminDocsView):
                     body = utils.parse_rst(body, 'tag', _('tag:') + tag_name)
                 for key in metadata:
                     metadata[key] = utils.parse_rst(metadata[key], 'tag', _('tag:') + tag_name)
-                if library in template.builtins:
+                if library in builtins:
                     tag_library = ''
                 else:
                     tag_library = module_name.split('.')[-1]
@@ -94,8 +95,8 @@ class TemplateFilterIndexView(BaseAdminDocsView):
         load_all_installed_template_libraries()
 
         filters = []
-        app_libs = list(six.iteritems(template.libraries))
-        builtin_libs = [(None, lib) for lib in template.builtins]
+        app_libs = list(six.iteritems(libraries))
+        builtin_libs = [(None, lib) for lib in builtins]
         for module_name, library in builtin_libs + app_libs:
             for filter_name, filter_func in library.filters.items():
                 title, body, metadata = utils.parse_docstring(filter_func.__doc__)
@@ -105,7 +106,7 @@ class TemplateFilterIndexView(BaseAdminDocsView):
                     body = utils.parse_rst(body, 'filter', _('filter:') + filter_name)
                 for key in metadata:
                     metadata[key] = utils.parse_rst(metadata[key], 'filter', _('filter:') + filter_name)
-                if library in template.builtins:
+                if library in builtins:
                     tag_library = ''
                 else:
                     tag_library = module_name.split('.')[-1]
@@ -313,7 +314,7 @@ class TemplateDetailView(BaseAdminDocsView):
 
 def load_all_installed_template_libraries():
     # Load/register all template tag libraries from installed apps.
-    for module_name in template.get_templatetags_modules():
+    for module_name in get_templatetags_modules():
         mod = import_module(module_name)
         if not hasattr(mod, '__file__'):
             # e.g. packages installed as eggs
@@ -330,8 +331,8 @@ def load_all_installed_template_libraries():
         else:
             for library_name in libraries:
                 try:
-                    template.get_library(library_name)
-                except template.InvalidTemplateLibrary:
+                    get_library(library_name)
+                except InvalidTemplateLibrary:
                     pass
 
 
diff --git a/django/template/__init__.py b/django/template/__init__.py
index a43ff50f01..24a51ac20d 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -1,80 +1,17 @@
-"""
-This is the Django template system.
-
-How it works:
-
-The Lexer.tokenize() function converts a template string (i.e., a string containing
-markup with custom template tags) to tokens, which can be either plain text
-(TOKEN_TEXT), variables (TOKEN_VAR) or block statements (TOKEN_BLOCK).
-
-The Parser() class takes a list of tokens in its constructor, and its parse()
-method returns a compiled template -- which is, under the hood, a list of
-Node objects.
-
-Each Node is responsible for creating some sort of output -- e.g. simple text
-(TextNode), variable values in a given context (VariableNode), results of basic
-logic (IfNode), results of looping (ForNode), or anything else. The core Node
-types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can
-define their own custom node types.
-
-Each Node has a render() method, which takes a Context and returns a string of
-the rendered node. For example, the render() method of a Variable Node returns
-the variable's value as a string. The render() method of a ForNode returns the
-rendered output of whatever was inside the loop, recursively.
-
-The Template class is a convenient wrapper that takes care of template
-compilation and rendering.
-
-Usage:
-
-The only thing you should ever use directly in this file is the Template class.
-Create a compiled template object with a template_string, then call render()
-with a context. In the compilation stage, the TemplateSyntaxError exception
-will be raised if the template doesn't have proper syntax.
-
-Sample code:
-
->>> from django import template
->>> s = u'<html>{% if test %}<h1>{{ varvalue }}</h1>{% endif %}</html>'
->>> t = template.Template(s)
-
-(t is now a compiled template, and its render() method can be called multiple
-times with multiple contexts)
-
->>> c = template.Context({'test':True, 'varvalue': 'Hello'})
->>> t.render(c)
-u'<html><h1>Hello</h1></html>'
->>> c = template.Context({'test':False, 'varvalue': 'Hello'})
->>> t.render(c)
-u'<html></html>'
-"""
-
-# Template lexing symbols
-from django.template.base import (ALLOWED_VARIABLE_CHARS, BLOCK_TAG_END,  # NOQA
-    BLOCK_TAG_START, COMMENT_TAG_END, COMMENT_TAG_START,
-    FILTER_ARGUMENT_SEPARATOR, FILTER_SEPARATOR, SINGLE_BRACE_END,
-    SINGLE_BRACE_START, TOKEN_BLOCK, TOKEN_COMMENT, TOKEN_TEXT, TOKEN_VAR,
-    TRANSLATOR_COMMENT_MARK, UNKNOWN_SOURCE, VARIABLE_ATTRIBUTE_SEPARATOR,
-    VARIABLE_TAG_END, VARIABLE_TAG_START, filter_re, tag_re)
-
-# Exceptions
-from django.template.base import (ContextPopException, InvalidTemplateLibrary,  # NOQA
-    TemplateDoesNotExist, TemplateEncodingError, TemplateSyntaxError,
-    VariableDoesNotExist)
+# Public exceptions
+from .base import (TemplateDoesNotExist, TemplateSyntaxError,           # NOQA
+                   VariableDoesNotExist)
+from .context import ContextPopException                                # NOQA
 
 # Template parts
-from django.template.base import (Context, FilterExpression, Lexer, Node,  # NOQA
-    NodeList, Parser, RequestContext, Origin, StringOrigin, Template,
-    TextNode, Token, TokenParser, Variable, VariableNode, constant_string,
-    filter_raw_string)
+from .base import (Context, Node, NodeList, RequestContext,             # NOQA
+                   StringOrigin, Template, Variable)
 
-# Compiling templates
-from django.template.base import (resolve_variable,  # NOQA
-    unescape_string_literal, generic_tag_compiler)
+# Deprecated in Django 1.8, will be removed in Django 2.0.
+from .base import resolve_variable                                      # NOQA
 
 # Library management
-from django.template.base import (Library, add_to_builtins, builtins,  # NOQA
-    get_library, get_templatetags_modules, get_text_list, import_library,
-    libraries)
+from .base import Library                                               # NOQA
+
 
 __all__ = ('Template', 'Context', 'RequestContext')
diff --git a/django/template/base.py b/django/template/base.py
index 4ac20b13e1..2917d8323d 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -1,3 +1,54 @@
+"""
+This is the Django template system.
+
+How it works:
+
+The Lexer.tokenize() function converts a template string (i.e., a string containing
+markup with custom template tags) to tokens, which can be either plain text
+(TOKEN_TEXT), variables (TOKEN_VAR) or block statements (TOKEN_BLOCK).
+
+The Parser() class takes a list of tokens in its constructor, and its parse()
+method returns a compiled template -- which is, under the hood, a list of
+Node objects.
+
+Each Node is responsible for creating some sort of output -- e.g. simple text
+(TextNode), variable values in a given context (VariableNode), results of basic
+logic (IfNode), results of looping (ForNode), or anything else. The core Node
+types are TextNode, VariableNode, IfNode and ForNode, but plugin modules can
+define their own custom node types.
+
+Each Node has a render() method, which takes a Context and returns a string of
+the rendered node. For example, the render() method of a Variable Node returns
+the variable's value as a string. The render() method of a ForNode returns the
+rendered output of whatever was inside the loop, recursively.
+
+The Template class is a convenient wrapper that takes care of template
+compilation and rendering.
+
+Usage:
+
+The only thing you should ever use directly in this file is the Template class.
+Create a compiled template object with a template_string, then call render()
+with a context. In the compilation stage, the TemplateSyntaxError exception
+will be raised if the template doesn't have proper syntax.
+
+Sample code:
+
+>>> from django import template
+>>> s = u'<html>{% if test %}<h1>{{ varvalue }}</h1>{% endif %}</html>'
+>>> t = template.Template(s)
+
+(t is now a compiled template, and its render() method can be called multiple
+times with multiple contexts)
+
+>>> c = template.Context({'test':True, 'varvalue': 'Hello'})
+>>> t.render(c)
+u'<html><h1>Hello</h1></html>'
+>>> c = template.Context({'test':False, 'varvalue': 'Hello'})
+>>> t.render(c)
+u'<html></html>'
+"""
+
 from __future__ import unicode_literals
 
 import re
diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py
index 0bfb4a9eff..ecaf84a035 100644
--- a/django/templatetags/i18n.py
+++ b/django/templatetags/i18n.py
@@ -3,9 +3,8 @@ import re
 import sys
 
 from django.conf import settings
-from django.template import (Node, Variable, TemplateSyntaxError,
-    TokenParser, Library, TOKEN_TEXT, TOKEN_VAR)
-from django.template.base import render_value_in_context
+from django.template import Library, Node, TemplateSyntaxError, Variable
+from django.template.base import render_value_in_context, TokenParser, TOKEN_TEXT, TOKEN_VAR
 from django.template.defaulttags import token_kwargs
 from django.utils import six
 from django.utils import translation
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index 2af2f1dd35..1d4d100206 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -544,8 +544,8 @@ def templatize(src, origin=None):
     does so by translating the Django translation tags into standard gettext
     function invocations.
     """
-    from django.template import (Lexer, TOKEN_TEXT, TOKEN_VAR, TOKEN_BLOCK,
-            TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK)
+    from django.template.base import (Lexer, TOKEN_TEXT, TOKEN_VAR,
+        TOKEN_BLOCK, TOKEN_COMMENT, TRANSLATOR_COMMENT_MARK)
     src = force_text(src, settings.FILE_CHARSET)
     out = StringIO('')
     message_context = None
diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt
index 6a164821b6..a4bc14332c 100644
--- a/docs/releases/1.8.txt
+++ b/docs/releases/1.8.txt
@@ -832,6 +832,14 @@ Django previously closed database connections between each test within a
 ``TestCase`` within a transaction. If some of your tests relied on the old
 behavior, you should have them inherit from ``TransactionTestCase`` instead.
 
+Cleanup of the ``django.template`` namespace
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If you've been relying on private APIs exposed in the ``django.template``
+module, you may have to import them from ``django.template.base`` instead.
+
+Also ``django.template.base.compile_string()`` was removed.
+
 Miscellaneous
 ~~~~~~~~~~~~~
 
@@ -906,8 +914,6 @@ Miscellaneous
   delete a key if ``set()`` fails. This is necessary to ensure the ``cache_db``
   session store always fetches the most current session data.
 
-* Private API ``django.template.compile_string`` was removed.
-
 * Private APIs ``override_template_loaders`` and ``override_with_test_loader``
   in ``django.test.utils`` were removed. Override ``TEMPLATE_LOADERS`` with
   ``override_settings`` instead.
diff --git a/tests/template_tests/test_nodelist.py b/tests/template_tests/test_nodelist.py
index bce75aeecb..836d1b6db9 100644
--- a/tests/template_tests/test_nodelist.py
+++ b/tests/template_tests/test_nodelist.py
@@ -1,6 +1,7 @@
 from unittest import TestCase
 
-from django.template import Context, Template, VariableNode
+from django.template import Context, Template
+from django.template.base import VariableNode
 from django.test import override_settings
 
 
diff --git a/tests/template_tests/test_parser.py b/tests/template_tests/test_parser.py
index 017e78a863..348428513b 100644
--- a/tests/template_tests/test_parser.py
+++ b/tests/template_tests/test_parser.py
@@ -5,8 +5,8 @@ from __future__ import unicode_literals
 
 from unittest import TestCase
 
-from django.template import (TokenParser, FilterExpression, Parser, Variable,
-    Template, TemplateSyntaxError, Library)
+from django.template import Library, Template, TemplateSyntaxError
+from django.template.base import FilterExpression, Parser, TokenParser, Variable
 from django.test import override_settings
 from django.utils import six
 
diff --git a/tests/template_tests/test_unicode.py b/tests/template_tests/test_unicode.py
index 1f333bfed2..02d3d0a51d 100644
--- a/tests/template_tests/test_unicode.py
+++ b/tests/template_tests/test_unicode.py
@@ -3,7 +3,8 @@ from __future__ import unicode_literals
 
 from unittest import TestCase
 
-from django.template import Template, TemplateEncodingError, Context
+from django.template import Template, Context
+from django.template.base import TemplateEncodingError
 from django.utils.safestring import SafeData
 from django.utils import six
 
diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py
index 2ba1cdb18a..80f926ea6f 100644
--- a/tests/template_tests/tests.py
+++ b/tests/template_tests/tests.py
@@ -8,7 +8,8 @@ import unittest
 from django import template
 from django.contrib.auth.models import Group
 from django.core import urlresolvers
-from django.template import loader, Context, RequestContext, Template, TemplateSyntaxError
+from django.template import (base as template_base, loader,
+    Context, RequestContext, Template, TemplateSyntaxError)
 from django.template.engine import Engine
 from django.template.loaders import app_directories, filesystem
 from django.test import RequestFactory, SimpleTestCase
@@ -245,7 +246,7 @@ class TemplateRegressionTests(SimpleTestCase):
 
     def test_token_smart_split(self):
         # Regression test for #7027
-        token = template.Token(template.TOKEN_BLOCK, 'sometag _("Page not found") value|yesno:_("yes,no")')
+        token = template_base.Token(template_base.TOKEN_BLOCK, 'sometag _("Page not found") value|yesno:_("yes,no")')
         split = token.split_contents()
         self.assertEqual(split, ["sometag", '_("Page not found")', 'value|yesno:_("yes,no")'])
 
diff --git a/tests/template_tests/utils.py b/tests/template_tests/utils.py
index 168eca73f8..ba8dcdd46f 100644
--- a/tests/template_tests/utils.py
+++ b/tests/template_tests/utils.py
@@ -6,7 +6,7 @@ import functools
 
 from django import template
 from django.template import Library
-from django.template.base import Context
+from django.template.base import Context, libraries
 from django.template.engine import Engine
 from django.template.loader import get_template
 from django.test.utils import override_settings
@@ -100,9 +100,11 @@ def upper(value):
 def register_test_tags(func):
     @functools.wraps(func)
     def inner(self):
-        template.libraries['testtags'] = register
-        func(self)
-        del template.libraries['testtags']
+        libraries['testtags'] = register
+        try:
+            func(self)
+        finally:
+            del libraries['testtags']
     return inner