From 7eefdbf7ab9f5bafe5baae2b877d93efc90e3044 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin 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'{% if test %}

{{ varvalue }}

{% endif %}' ->>> 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'

Hello

' ->>> c = template.Context({'test':False, 'varvalue': 'Hello'}) ->>> t.render(c) -u'' -""" - -# 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'{% if test %}

{{ varvalue }}

{% endif %}' +>>> 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'

Hello

' +>>> c = template.Context({'test':False, 'varvalue': 'Hello'}) +>>> t.render(c) +u'' +""" + 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