mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	* Converted the ``libraries`` and ``builtins`` globals of ``django.template.base`` into properties of the Engine class. * Added a public API for explicit registration of libraries and builtins.
This commit is contained in:
		| @@ -12,12 +12,7 @@ from django.core import urlresolvers | ||||
| from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist | ||||
| from django.db import models | ||||
| from django.http import Http404 | ||||
| from django.template.base import ( | ||||
|     InvalidTemplateLibrary, builtins, get_library, get_templatetags_modules, | ||||
|     libraries, | ||||
| ) | ||||
| from django.template.engine import Engine | ||||
| from django.utils._os import upath | ||||
| from django.utils.decorators import method_decorator | ||||
| from django.utils.translation import ugettext as _ | ||||
| from django.views.generic import TemplateView | ||||
| @@ -60,11 +55,15 @@ class TemplateTagIndexView(BaseAdminDocsView): | ||||
|     template_name = 'admin_doc/template_tag_index.html' | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         load_all_installed_template_libraries() | ||||
|  | ||||
|         tags = [] | ||||
|         app_libs = list(libraries.items()) | ||||
|         builtin_libs = [(None, lib) for lib in builtins] | ||||
|         try: | ||||
|             engine = Engine.get_default() | ||||
|         except ImproperlyConfigured: | ||||
|             # Non-trivial TEMPLATES settings aren't supported (#24125). | ||||
|             pass | ||||
|         else: | ||||
|             app_libs = sorted(engine.template_libraries.items()) | ||||
|             builtin_libs = [('', lib) for lib in engine.template_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__) | ||||
| @@ -74,9 +73,6 @@ 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 builtins: | ||||
|                     tag_library = '' | ||||
|                 else: | ||||
|                     tag_library = module_name.split('.')[-1] | ||||
|                     tags.append({ | ||||
|                         'name': tag_name, | ||||
| @@ -93,11 +89,15 @@ class TemplateFilterIndexView(BaseAdminDocsView): | ||||
|     template_name = 'admin_doc/template_filter_index.html' | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         load_all_installed_template_libraries() | ||||
|  | ||||
|         filters = [] | ||||
|         app_libs = list(libraries.items()) | ||||
|         builtin_libs = [(None, lib) for lib in builtins] | ||||
|         try: | ||||
|             engine = Engine.get_default() | ||||
|         except ImproperlyConfigured: | ||||
|             # Non-trivial TEMPLATES settings aren't supported (#24125). | ||||
|             pass | ||||
|         else: | ||||
|             app_libs = sorted(engine.template_libraries.items()) | ||||
|             builtin_libs = [('', lib) for lib in engine.template_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__) | ||||
| @@ -107,9 +107,6 @@ 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 builtins: | ||||
|                     tag_library = '' | ||||
|                 else: | ||||
|                     tag_library = module_name.split('.')[-1] | ||||
|                     filters.append({ | ||||
|                         'name': filter_name, | ||||
| @@ -320,29 +317,6 @@ class TemplateDetailView(BaseAdminDocsView): | ||||
| # Helper functions # | ||||
| #################### | ||||
|  | ||||
| def load_all_installed_template_libraries(): | ||||
|     # Load/register all template tag libraries from installed apps. | ||||
|     for module_name in get_templatetags_modules(): | ||||
|         mod = import_module(module_name) | ||||
|         if not hasattr(mod, '__file__'): | ||||
|             # e.g. packages installed as eggs | ||||
|             continue | ||||
|  | ||||
|         try: | ||||
|             libraries = [ | ||||
|                 os.path.splitext(p)[0] | ||||
|                 for p in os.listdir(os.path.dirname(upath(mod.__file__))) | ||||
|                 if p.endswith('.py') and p[0].isalpha() | ||||
|             ] | ||||
|         except OSError: | ||||
|             continue | ||||
|         else: | ||||
|             for library_name in libraries: | ||||
|                 try: | ||||
|                     get_library(library_name) | ||||
|                 except InvalidTemplateLibrary: | ||||
|                     pass | ||||
|  | ||||
|  | ||||
| def get_return_data_type(func_name): | ||||
|     """Return a somewhat-helpful data type given a function name""" | ||||
|   | ||||
| @@ -66,7 +66,7 @@ from .base import (Context, Node, NodeList, Origin, RequestContext,     # NOQA | ||||
| from .base import resolve_variable                                      # NOQA | ||||
|  | ||||
| # Library management | ||||
| from .base import Library                                               # NOQA | ||||
| from .library import Library                                            # NOQA | ||||
|  | ||||
|  | ||||
| __all__ += ('Template', 'Context', 'RequestContext') | ||||
|   | ||||
| @@ -3,11 +3,15 @@ from __future__ import absolute_import | ||||
|  | ||||
| import sys | ||||
| import warnings | ||||
| from importlib import import_module | ||||
| from pkgutil import walk_packages | ||||
|  | ||||
| from django.apps import apps | ||||
| from django.conf import settings | ||||
| from django.template import TemplateDoesNotExist | ||||
| from django.template.context import Context, RequestContext, make_context | ||||
| from django.template.engine import Engine, _dirs_undefined | ||||
| from django.template.library import InvalidTemplateLibrary | ||||
| from django.utils import six | ||||
| from django.utils.deprecation import RemovedInDjango20Warning | ||||
|  | ||||
| @@ -23,6 +27,8 @@ class DjangoTemplates(BaseEngine): | ||||
|         options = params.pop('OPTIONS').copy() | ||||
|         options.setdefault('debug', settings.DEBUG) | ||||
|         options.setdefault('file_charset', settings.FILE_CHARSET) | ||||
|         libraries = options.get('libraries', {}) | ||||
|         options['libraries'] = self.get_templatetag_libraries(libraries) | ||||
|         super(DjangoTemplates, self).__init__(params) | ||||
|         self.engine = Engine(self.dirs, self.app_dirs, **options) | ||||
|  | ||||
| @@ -35,6 +41,15 @@ class DjangoTemplates(BaseEngine): | ||||
|         except TemplateDoesNotExist as exc: | ||||
|             reraise(exc, self) | ||||
|  | ||||
|     def get_templatetag_libraries(self, custom_libraries): | ||||
|         """ | ||||
|         Return a collation of template tag libraries from installed | ||||
|         applications and the supplied custom_libraries argument. | ||||
|         """ | ||||
|         libraries = get_installed_libraries() | ||||
|         libraries.update(custom_libraries) | ||||
|         return libraries | ||||
|  | ||||
|  | ||||
| class Template(object): | ||||
|  | ||||
| @@ -90,3 +105,48 @@ def reraise(exc, backend): | ||||
|     if hasattr(exc, 'template_debug'): | ||||
|         new.template_debug = exc.template_debug | ||||
|     six.reraise(exc.__class__, new, sys.exc_info()[2]) | ||||
|  | ||||
|  | ||||
| def get_installed_libraries(): | ||||
|     """ | ||||
|     Return the built-in template tag libraries and those from installed | ||||
|     applications. Libraries are stored in a dictionary where keys are the | ||||
|     individual module names, not the full module paths. Example: | ||||
|     django.templatetags.i18n is stored as i18n. | ||||
|     """ | ||||
|     libraries = {} | ||||
|     candidates = ['django.templatetags'] | ||||
|     candidates.extend( | ||||
|         '%s.templatetags' % app_config.name | ||||
|         for app_config in apps.get_app_configs()) | ||||
|  | ||||
|     for candidate in candidates: | ||||
|         try: | ||||
|             pkg = import_module(candidate) | ||||
|         except ImportError: | ||||
|             # No templatetags package defined. This is safe to ignore. | ||||
|             continue | ||||
|  | ||||
|         if hasattr(pkg, '__path__'): | ||||
|             for name in get_package_libraries(pkg): | ||||
|                 libraries[name[len(candidate) + 1:]] = name | ||||
|  | ||||
|     return libraries | ||||
|  | ||||
|  | ||||
| def get_package_libraries(pkg): | ||||
|     """ | ||||
|     Recursively yield template tag libraries defined in submodules of a | ||||
|     package. | ||||
|     """ | ||||
|     for entry in walk_packages(pkg.__path__, pkg.__name__ + '.'): | ||||
|         try: | ||||
|             module = import_module(entry[1]) | ||||
|         except ImportError as e: | ||||
|             raise InvalidTemplateLibrary( | ||||
|                 "Invalid template library specified. ImportError raised when " | ||||
|                 "trying to load '%s': %s" % (entry[1], e) | ||||
|             ) | ||||
|  | ||||
|         if hasattr(module, 'register'): | ||||
|             yield entry[1] | ||||
|   | ||||
| @@ -54,25 +54,18 @@ from __future__ import unicode_literals | ||||
| import logging | ||||
| import re | ||||
| import warnings | ||||
| from functools import partial | ||||
| from importlib import import_module | ||||
| from inspect import getargspec, getcallargs | ||||
|  | ||||
| from django.apps import apps | ||||
| from django.template.context import (  # NOQA: imported for backwards compatibility | ||||
|     BaseContext, Context, ContextPopException, RequestContext, | ||||
| ) | ||||
| from django.utils import lru_cache, six | ||||
| from django.utils.deprecation import ( | ||||
|     RemovedInDjango20Warning, RemovedInDjango21Warning, | ||||
| ) | ||||
| from django.utils import six | ||||
| from django.utils.deprecation import RemovedInDjango20Warning | ||||
| from django.utils.encoding import ( | ||||
|     force_str, force_text, python_2_unicode_compatible, | ||||
| ) | ||||
| from django.utils.formats import localize | ||||
| from django.utils.html import conditional_escape, escape | ||||
| from django.utils.itercompat import is_iterable | ||||
| from django.utils.module_loading import module_has_submodule | ||||
| from django.utils.safestring import ( | ||||
|     EscapeData, SafeData, mark_for_escaping, mark_safe, | ||||
| ) | ||||
| @@ -123,11 +116,6 @@ tag_re = (re.compile('(%s.*?%s|%s.*?%s|%s.*?%s)' % | ||||
|            re.escape(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END), | ||||
|            re.escape(COMMENT_TAG_START), re.escape(COMMENT_TAG_END)))) | ||||
|  | ||||
| # global dictionary of libraries that have been loaded using get_library | ||||
| libraries = {} | ||||
| # global list of libraries to load by default for a new parser | ||||
| builtins = [] | ||||
|  | ||||
| logger = logging.getLogger('django.template') | ||||
|  | ||||
|  | ||||
| @@ -146,10 +134,6 @@ class VariableDoesNotExist(Exception): | ||||
|         return self.msg % tuple(force_text(p, errors='replace') for p in self.params) | ||||
|  | ||||
|  | ||||
| class InvalidTemplateLibrary(Exception): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class Origin(object): | ||||
|     def __init__(self, name, template_name=None, loader=None): | ||||
|         self.name = name | ||||
| @@ -232,7 +216,9 @@ class Template(object): | ||||
|             lexer = Lexer(self.source) | ||||
|  | ||||
|         tokens = lexer.tokenize() | ||||
|         parser = Parser(tokens) | ||||
|         parser = Parser( | ||||
|             tokens, self.engine.template_libraries, self.engine.template_builtins, | ||||
|         ) | ||||
|  | ||||
|         try: | ||||
|             return parser.parse() | ||||
| @@ -452,13 +438,20 @@ class DebugLexer(Lexer): | ||||
|  | ||||
|  | ||||
| class Parser(object): | ||||
|     def __init__(self, tokens): | ||||
|     def __init__(self, tokens, libraries=None, builtins=None): | ||||
|         self.tokens = tokens | ||||
|         self.tags = {} | ||||
|         self.filters = {} | ||||
|         self.command_stack = [] | ||||
|         for lib in builtins: | ||||
|             self.add_library(lib) | ||||
|  | ||||
|         if libraries is None: | ||||
|             libraries = {} | ||||
|         if builtins is None: | ||||
|             builtins = [] | ||||
|  | ||||
|         self.libraries = libraries | ||||
|         for builtin in builtins: | ||||
|             self.add_library(builtin) | ||||
|  | ||||
|     def parse(self, parse_until=None): | ||||
|         """ | ||||
| @@ -1073,377 +1066,3 @@ def token_kwargs(bits, parser, support_legacy=False): | ||||
|                 return kwargs | ||||
|             del bits[:1] | ||||
|     return kwargs | ||||
|  | ||||
|  | ||||
| def parse_bits(parser, bits, params, varargs, varkw, defaults, | ||||
|                takes_context, name): | ||||
|     """ | ||||
|     Parses bits for template tag helpers simple_tag and inclusion_tag, in | ||||
|     particular by detecting syntax errors and by extracting positional and | ||||
|     keyword arguments. | ||||
|     """ | ||||
|     if takes_context: | ||||
|         if params[0] == 'context': | ||||
|             params = params[1:] | ||||
|         else: | ||||
|             raise TemplateSyntaxError( | ||||
|                 "'%s' is decorated with takes_context=True so it must " | ||||
|                 "have a first argument of 'context'" % name) | ||||
|     args = [] | ||||
|     kwargs = {} | ||||
|     unhandled_params = list(params) | ||||
|     for bit in bits: | ||||
|         # First we try to extract a potential kwarg from the bit | ||||
|         kwarg = token_kwargs([bit], parser) | ||||
|         if kwarg: | ||||
|             # The kwarg was successfully extracted | ||||
|             param, value = kwarg.popitem() | ||||
|             if param not in params and varkw is None: | ||||
|                 # An unexpected keyword argument was supplied | ||||
|                 raise TemplateSyntaxError( | ||||
|                     "'%s' received unexpected keyword argument '%s'" % | ||||
|                     (name, param)) | ||||
|             elif param in kwargs: | ||||
|                 # The keyword argument has already been supplied once | ||||
|                 raise TemplateSyntaxError( | ||||
|                     "'%s' received multiple values for keyword argument '%s'" % | ||||
|                     (name, param)) | ||||
|             else: | ||||
|                 # All good, record the keyword argument | ||||
|                 kwargs[str(param)] = value | ||||
|                 if param in unhandled_params: | ||||
|                     # If using the keyword syntax for a positional arg, then | ||||
|                     # consume it. | ||||
|                     unhandled_params.remove(param) | ||||
|         else: | ||||
|             if kwargs: | ||||
|                 raise TemplateSyntaxError( | ||||
|                     "'%s' received some positional argument(s) after some " | ||||
|                     "keyword argument(s)" % name) | ||||
|             else: | ||||
|                 # Record the positional argument | ||||
|                 args.append(parser.compile_filter(bit)) | ||||
|                 try: | ||||
|                     # Consume from the list of expected positional arguments | ||||
|                     unhandled_params.pop(0) | ||||
|                 except IndexError: | ||||
|                     if varargs is None: | ||||
|                         raise TemplateSyntaxError( | ||||
|                             "'%s' received too many positional arguments" % | ||||
|                             name) | ||||
|     if defaults is not None: | ||||
|         # Consider the last n params handled, where n is the | ||||
|         # number of defaults. | ||||
|         unhandled_params = unhandled_params[:-len(defaults)] | ||||
|     if unhandled_params: | ||||
|         # Some positional arguments were not supplied | ||||
|         raise TemplateSyntaxError( | ||||
|             "'%s' did not receive value(s) for the argument(s): %s" % | ||||
|             (name, ", ".join("'%s'" % p for p in unhandled_params))) | ||||
|     return args, kwargs | ||||
|  | ||||
|  | ||||
| def generic_tag_compiler(parser, token, params, varargs, varkw, defaults, | ||||
|                          name, takes_context, node_class): | ||||
|     """ | ||||
|     Returns a template.Node subclass. | ||||
|     """ | ||||
|     bits = token.split_contents()[1:] | ||||
|     args, kwargs = parse_bits(parser, bits, params, varargs, varkw, | ||||
|                               defaults, takes_context, name) | ||||
|     return node_class(takes_context, args, kwargs) | ||||
|  | ||||
|  | ||||
| class TagHelperNode(Node): | ||||
|     """ | ||||
|     Base class for tag helper nodes such as SimpleNode and InclusionNode. | ||||
|     Manages the positional and keyword arguments to be passed to the decorated | ||||
|     function. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, takes_context, args, kwargs): | ||||
|         self.takes_context = takes_context | ||||
|         self.args = args | ||||
|         self.kwargs = kwargs | ||||
|  | ||||
|     def get_resolved_arguments(self, context): | ||||
|         resolved_args = [var.resolve(context) for var in self.args] | ||||
|         if self.takes_context: | ||||
|             resolved_args = [context] + resolved_args | ||||
|         resolved_kwargs = {k: v.resolve(context) for k, v in self.kwargs.items()} | ||||
|         return resolved_args, resolved_kwargs | ||||
|  | ||||
|  | ||||
| class Library(object): | ||||
|     def __init__(self): | ||||
|         self.filters = {} | ||||
|         self.tags = {} | ||||
|  | ||||
|     def tag(self, name=None, compile_function=None): | ||||
|         if name is None and compile_function is None: | ||||
|             # @register.tag() | ||||
|             return self.tag_function | ||||
|         elif name is not None and compile_function is None: | ||||
|             if callable(name): | ||||
|                 # @register.tag | ||||
|                 return self.tag_function(name) | ||||
|             else: | ||||
|                 # @register.tag('somename') or @register.tag(name='somename') | ||||
|                 def dec(func): | ||||
|                     return self.tag(name, func) | ||||
|                 return dec | ||||
|         elif name is not None and compile_function is not None: | ||||
|             # register.tag('somename', somefunc) | ||||
|             self.tags[name] = compile_function | ||||
|             return compile_function | ||||
|         else: | ||||
|             raise InvalidTemplateLibrary("Unsupported arguments to " | ||||
|                 "Library.tag: (%r, %r)", (name, compile_function)) | ||||
|  | ||||
|     def tag_function(self, func): | ||||
|         self.tags[getattr(func, "_decorated_function", func).__name__] = func | ||||
|         return func | ||||
|  | ||||
|     def filter(self, name=None, filter_func=None, **flags): | ||||
|         if name is None and filter_func is None: | ||||
|             # @register.filter() | ||||
|             def dec(func): | ||||
|                 return self.filter_function(func, **flags) | ||||
|             return dec | ||||
|  | ||||
|         elif name is not None and filter_func is None: | ||||
|             if callable(name): | ||||
|                 # @register.filter | ||||
|                 return self.filter_function(name, **flags) | ||||
|             else: | ||||
|                 # @register.filter('somename') or @register.filter(name='somename') | ||||
|                 def dec(func): | ||||
|                     return self.filter(name, func, **flags) | ||||
|                 return dec | ||||
|  | ||||
|         elif name is not None and filter_func is not None: | ||||
|             # register.filter('somename', somefunc) | ||||
|             self.filters[name] = filter_func | ||||
|             for attr in ('expects_localtime', 'is_safe', 'needs_autoescape'): | ||||
|                 if attr in flags: | ||||
|                     value = flags[attr] | ||||
|                     # set the flag on the filter for FilterExpression.resolve | ||||
|                     setattr(filter_func, attr, value) | ||||
|                     # set the flag on the innermost decorated function | ||||
|                     # for decorators that need it e.g. stringfilter | ||||
|                     if hasattr(filter_func, "_decorated_function"): | ||||
|                         setattr(filter_func._decorated_function, attr, value) | ||||
|             filter_func._filter_name = name | ||||
|             return filter_func | ||||
|         else: | ||||
|             raise InvalidTemplateLibrary("Unsupported arguments to " | ||||
|                 "Library.filter: (%r, %r)", (name, filter_func)) | ||||
|  | ||||
|     def filter_function(self, func, **flags): | ||||
|         name = getattr(func, "_decorated_function", func).__name__ | ||||
|         return self.filter(name, func, **flags) | ||||
|  | ||||
|     def simple_tag(self, func=None, takes_context=None, name=None): | ||||
|         def dec(func): | ||||
|             params, varargs, varkw, defaults = getargspec(func) | ||||
|  | ||||
|             class SimpleNode(TagHelperNode): | ||||
|                 def __init__(self, takes_context, args, kwargs, target_var): | ||||
|                     super(SimpleNode, self).__init__(takes_context, args, kwargs) | ||||
|                     self.target_var = target_var | ||||
|  | ||||
|                 def render(self, context): | ||||
|                     resolved_args, resolved_kwargs = self.get_resolved_arguments(context) | ||||
|                     output = func(*resolved_args, **resolved_kwargs) | ||||
|                     if self.target_var is not None: | ||||
|                         context[self.target_var] = output | ||||
|                         return '' | ||||
|                     return output | ||||
|  | ||||
|             function_name = (name or | ||||
|                 getattr(func, '_decorated_function', func).__name__) | ||||
|  | ||||
|             def compile_func(parser, token): | ||||
|                 bits = token.split_contents()[1:] | ||||
|                 target_var = None | ||||
|                 if len(bits) >= 2 and bits[-2] == 'as': | ||||
|                     target_var = bits[-1] | ||||
|                     bits = bits[:-2] | ||||
|                 args, kwargs = parse_bits(parser, bits, params, | ||||
|                     varargs, varkw, defaults, takes_context, function_name) | ||||
|                 return SimpleNode(takes_context, args, kwargs, target_var) | ||||
|  | ||||
|             compile_func.__doc__ = func.__doc__ | ||||
|             self.tag(function_name, compile_func) | ||||
|             return func | ||||
|  | ||||
|         if func is None: | ||||
|             # @register.simple_tag(...) | ||||
|             return dec | ||||
|         elif callable(func): | ||||
|             # @register.simple_tag | ||||
|             return dec(func) | ||||
|         else: | ||||
|             raise TemplateSyntaxError("Invalid arguments provided to simple_tag") | ||||
|  | ||||
|     def assignment_tag(self, func=None, takes_context=None, name=None): | ||||
|         warnings.warn( | ||||
|             "assignment_tag() is deprecated. Use simple_tag() instead", | ||||
|             RemovedInDjango21Warning, | ||||
|             stacklevel=2, | ||||
|         ) | ||||
|         return self.simple_tag(func, takes_context, name) | ||||
|  | ||||
|     def inclusion_tag(self, file_name, takes_context=False, name=None): | ||||
|         def dec(func): | ||||
|             params, varargs, varkw, defaults = getargspec(func) | ||||
|  | ||||
|             class InclusionNode(TagHelperNode): | ||||
|  | ||||
|                 def render(self, context): | ||||
|                     """ | ||||
|                     Renders the specified template and context. Caches the | ||||
|                     template object in render_context to avoid reparsing and | ||||
|                     loading when used in a for loop. | ||||
|                     """ | ||||
|                     resolved_args, resolved_kwargs = self.get_resolved_arguments(context) | ||||
|                     _dict = func(*resolved_args, **resolved_kwargs) | ||||
|  | ||||
|                     t = context.render_context.get(self) | ||||
|                     if t is None: | ||||
|                         if isinstance(file_name, Template): | ||||
|                             t = file_name | ||||
|                         elif isinstance(getattr(file_name, 'template', None), Template): | ||||
|                             t = file_name.template | ||||
|                         elif not isinstance(file_name, six.string_types) and is_iterable(file_name): | ||||
|                             t = context.template.engine.select_template(file_name) | ||||
|                         else: | ||||
|                             t = context.template.engine.get_template(file_name) | ||||
|                         context.render_context[self] = t | ||||
|                     new_context = context.new(_dict) | ||||
|                     # Copy across the CSRF token, if present, because | ||||
|                     # inclusion tags are often used for forms, and we need | ||||
|                     # instructions for using CSRF protection to be as simple | ||||
|                     # as possible. | ||||
|                     csrf_token = context.get('csrf_token') | ||||
|                     if csrf_token is not None: | ||||
|                         new_context['csrf_token'] = csrf_token | ||||
|                     return t.render(new_context) | ||||
|  | ||||
|             function_name = (name or | ||||
|                 getattr(func, '_decorated_function', func).__name__) | ||||
|             compile_func = partial(generic_tag_compiler, | ||||
|                 params=params, varargs=varargs, varkw=varkw, | ||||
|                 defaults=defaults, name=function_name, | ||||
|                 takes_context=takes_context, node_class=InclusionNode) | ||||
|             compile_func.__doc__ = func.__doc__ | ||||
|             self.tag(function_name, compile_func) | ||||
|             return func | ||||
|         return dec | ||||
|  | ||||
|  | ||||
| def is_library_missing(name): | ||||
|     """Check if library that failed to load cannot be found under any | ||||
|     templatetags directory or does exist but fails to import. | ||||
|  | ||||
|     Non-existing condition is checked recursively for each subpackage in cases | ||||
|     like <appdir>/templatetags/subpackage/package/module.py. | ||||
|     """ | ||||
|     # Don't bother to check if '.' is in name since any name will be prefixed | ||||
|     # with some template root. | ||||
|     path, module = name.rsplit('.', 1) | ||||
|     try: | ||||
|         package = import_module(path) | ||||
|         return not module_has_submodule(package, module) | ||||
|     except ImportError: | ||||
|         return is_library_missing(path) | ||||
|  | ||||
|  | ||||
| def import_library(taglib_module): | ||||
|     """ | ||||
|     Load a template tag library module. | ||||
|  | ||||
|     Verifies that the library contains a 'register' attribute, and | ||||
|     returns that attribute as the representation of the library | ||||
|     """ | ||||
|     try: | ||||
|         mod = import_module(taglib_module) | ||||
|     except ImportError as e: | ||||
|         # If the ImportError is because the taglib submodule does not exist, | ||||
|         # that's not an error that should be raised. If the submodule exists | ||||
|         # and raised an ImportError on the attempt to load it, that we want | ||||
|         # to raise. | ||||
|         if is_library_missing(taglib_module): | ||||
|             return None | ||||
|         else: | ||||
|             raise InvalidTemplateLibrary("ImportError raised loading %s: %s" % | ||||
|                                          (taglib_module, e)) | ||||
|     try: | ||||
|         return mod.register | ||||
|     except AttributeError: | ||||
|         raise InvalidTemplateLibrary("Template library %s does not have " | ||||
|                                      "a variable named 'register'" % | ||||
|                                      taglib_module) | ||||
|  | ||||
|  | ||||
| @lru_cache.lru_cache() | ||||
| def get_templatetags_modules(): | ||||
|     """ | ||||
|     Return the list of all available template tag modules. | ||||
|  | ||||
|     Caches the result for faster access. | ||||
|     """ | ||||
|     templatetags_modules_candidates = ['django.templatetags'] | ||||
|     templatetags_modules_candidates.extend( | ||||
|         '%s.templatetags' % app_config.name | ||||
|         for app_config in apps.get_app_configs()) | ||||
|  | ||||
|     templatetags_modules = [] | ||||
|     for templatetag_module in templatetags_modules_candidates: | ||||
|         try: | ||||
|             import_module(templatetag_module) | ||||
|         except ImportError: | ||||
|             continue | ||||
|         else: | ||||
|             templatetags_modules.append(templatetag_module) | ||||
|     return templatetags_modules | ||||
|  | ||||
|  | ||||
| def get_library(library_name): | ||||
|     """ | ||||
|     Load the template library module with the given name. | ||||
|  | ||||
|     If library is not already loaded loop over all templatetags modules | ||||
|     to locate it. | ||||
|  | ||||
|     {% load somelib %} and {% load someotherlib %} loops twice. | ||||
|  | ||||
|     Subsequent loads eg. {% load somelib %} in the same process will grab | ||||
|     the cached module from libraries. | ||||
|     """ | ||||
|     lib = libraries.get(library_name) | ||||
|     if not lib: | ||||
|         templatetags_modules = get_templatetags_modules() | ||||
|         tried_modules = [] | ||||
|         for module in templatetags_modules: | ||||
|             taglib_module = '%s.%s' % (module, library_name) | ||||
|             tried_modules.append(taglib_module) | ||||
|             lib = import_library(taglib_module) | ||||
|             if lib: | ||||
|                 libraries[library_name] = lib | ||||
|                 break | ||||
|         if not lib: | ||||
|             raise InvalidTemplateLibrary("Template library %s not found, " | ||||
|                                          "tried %s" % | ||||
|                                          (library_name, | ||||
|                                           ','.join(tried_modules))) | ||||
|     return lib | ||||
|  | ||||
|  | ||||
| def add_to_builtins(module): | ||||
|     builtins.append(import_library(module)) | ||||
|  | ||||
|  | ||||
| add_to_builtins('django.template.defaulttags') | ||||
| add_to_builtins('django.template.defaultfilters') | ||||
| add_to_builtins('django.template.loader_tags') | ||||
|   | ||||
| @@ -25,7 +25,8 @@ from django.utils.text import ( | ||||
| from django.utils.timesince import timesince, timeuntil | ||||
| from django.utils.translation import ugettext, ungettext | ||||
|  | ||||
| from .base import Library, Variable, VariableDoesNotExist | ||||
| from .base import Variable, VariableDoesNotExist | ||||
| from .library import Library | ||||
|  | ||||
| register = Library() | ||||
|  | ||||
|   | ||||
| @@ -19,12 +19,12 @@ from django.utils.safestring import mark_safe | ||||
| from .base import ( | ||||
|     BLOCK_TAG_END, BLOCK_TAG_START, COMMENT_TAG_END, COMMENT_TAG_START, | ||||
|     SINGLE_BRACE_END, SINGLE_BRACE_START, VARIABLE_ATTRIBUTE_SEPARATOR, | ||||
|     VARIABLE_TAG_END, VARIABLE_TAG_START, Context, InvalidTemplateLibrary, | ||||
|     Library, Node, NodeList, Template, TemplateSyntaxError, | ||||
|     VariableDoesNotExist, get_library, kwarg_re, render_value_in_context, | ||||
|     token_kwargs, | ||||
|     VARIABLE_TAG_END, VARIABLE_TAG_START, Context, Node, NodeList, Template, | ||||
|     TemplateSyntaxError, VariableDoesNotExist, kwarg_re, | ||||
|     render_value_in_context, token_kwargs, | ||||
| ) | ||||
| from .defaultfilters import date | ||||
| from .library import Library | ||||
| from .smartif import IfParser, Literal | ||||
|  | ||||
| register = Library() | ||||
| @@ -1121,10 +1121,43 @@ def ssi(parser, token): | ||||
|     return SsiNode(filepath, parsed) | ||||
|  | ||||
|  | ||||
| def find_library(parser, name): | ||||
|     try: | ||||
|         return parser.libraries[name] | ||||
|     except KeyError: | ||||
|         raise TemplateSyntaxError( | ||||
|             "'%s' is not a registered tag library. Must be one of:\n%s" % ( | ||||
|                 name, "\n".join(sorted(parser.libraries.keys())), | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|  | ||||
| def load_from_library(library, label, names): | ||||
|     """ | ||||
|     Return a subset of tags and filters from a library. | ||||
|     """ | ||||
|     subset = Library() | ||||
|     for name in names: | ||||
|         found = False | ||||
|         if name in library.tags: | ||||
|             found = True | ||||
|             subset.tags[name] = library.tags[name] | ||||
|         if name in library.filters: | ||||
|             found = True | ||||
|             subset.filters[name] = library.filters[name] | ||||
|         if found is False: | ||||
|             raise TemplateSyntaxError( | ||||
|                 "'%s' is not a valid tag or filter in tag library '%s'" % ( | ||||
|                     name, label, | ||||
|                 ), | ||||
|             ) | ||||
|     return subset | ||||
|  | ||||
|  | ||||
| @register.tag | ||||
| def load(parser, token): | ||||
|     """ | ||||
|     Loads a custom template tag set. | ||||
|     Loads a custom template tag library into the parser. | ||||
|  | ||||
|     For example, to load the template tags in | ||||
|     ``django/templatetags/news/photos.py``:: | ||||
| @@ -1140,35 +1173,16 @@ def load(parser, token): | ||||
|     # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments | ||||
|     bits = token.contents.split() | ||||
|     if len(bits) >= 4 and bits[-2] == "from": | ||||
|         try: | ||||
|             taglib = bits[-1] | ||||
|             lib = get_library(taglib) | ||||
|         except InvalidTemplateLibrary as e: | ||||
|             raise TemplateSyntaxError("'%s' is not a valid tag library: %s" % | ||||
|                                       (taglib, e)) | ||||
|         # from syntax is used; load individual tags from the library | ||||
|         name = bits[-1] | ||||
|         lib = find_library(parser, name) | ||||
|         subset = load_from_library(lib, name, bits[1:-2]) | ||||
|         parser.add_library(subset) | ||||
|     else: | ||||
|             temp_lib = Library() | ||||
|             for name in bits[1:-2]: | ||||
|                 if name in lib.tags: | ||||
|                     temp_lib.tags[name] = lib.tags[name] | ||||
|                     # a name could be a tag *and* a filter, so check for both | ||||
|                     if name in lib.filters: | ||||
|                         temp_lib.filters[name] = lib.filters[name] | ||||
|                 elif name in lib.filters: | ||||
|                     temp_lib.filters[name] = lib.filters[name] | ||||
|                 else: | ||||
|                     raise TemplateSyntaxError("'%s' is not a valid tag or filter in tag library '%s'" % | ||||
|                                               (name, taglib)) | ||||
|             parser.add_library(temp_lib) | ||||
|     else: | ||||
|         for taglib in bits[1:]: | ||||
|             # add the library to the parser | ||||
|             try: | ||||
|                 lib = get_library(taglib) | ||||
|         # one or more libraries are specified; load and add them to the parser | ||||
|         for name in bits[1:]: | ||||
|             lib = find_library(parser, name) | ||||
|             parser.add_library(lib) | ||||
|             except InvalidTemplateLibrary as e: | ||||
|                 raise TemplateSyntaxError("'%s' is not a valid tag library: %s" % | ||||
|                                           (taglib, e)) | ||||
|     return LoadNode() | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -9,6 +9,7 @@ from django.utils.module_loading import import_string | ||||
| from .base import Context, Template | ||||
| from .context import _builtin_context_processors | ||||
| from .exceptions import TemplateDoesNotExist | ||||
| from .library import import_library | ||||
|  | ||||
| _context_instance_undefined = object() | ||||
| _dictionary_undefined = object() | ||||
| @@ -16,11 +17,16 @@ _dirs_undefined = object() | ||||
|  | ||||
|  | ||||
| class Engine(object): | ||||
|     default_builtins = [ | ||||
|         'django.template.defaulttags', | ||||
|         'django.template.defaultfilters', | ||||
|         'django.template.loader_tags', | ||||
|     ] | ||||
|  | ||||
|     def __init__(self, dirs=None, app_dirs=False, | ||||
|                  allowed_include_roots=None, context_processors=None, | ||||
|                  debug=False, loaders=None, string_if_invalid='', | ||||
|                  file_charset='utf-8'): | ||||
|                  file_charset='utf-8', libraries=None, builtins=None): | ||||
|         if dirs is None: | ||||
|             dirs = [] | ||||
|         if allowed_include_roots is None: | ||||
| @@ -35,6 +41,10 @@ class Engine(object): | ||||
|             if app_dirs: | ||||
|                 raise ImproperlyConfigured( | ||||
|                     "app_dirs must not be set when loaders is defined.") | ||||
|         if libraries is None: | ||||
|             libraries = {} | ||||
|         if builtins is None: | ||||
|             builtins = [] | ||||
|  | ||||
|         if isinstance(allowed_include_roots, six.string_types): | ||||
|             raise ImproperlyConfigured( | ||||
| @@ -48,6 +58,10 @@ class Engine(object): | ||||
|         self.loaders = loaders | ||||
|         self.string_if_invalid = string_if_invalid | ||||
|         self.file_charset = file_charset | ||||
|         self.libraries = libraries | ||||
|         self.template_libraries = self.get_template_libraries(libraries) | ||||
|         self.builtins = self.default_builtins + builtins | ||||
|         self.template_builtins = self.get_template_builtins(self.builtins) | ||||
|  | ||||
|     @staticmethod | ||||
|     @lru_cache.lru_cache() | ||||
| @@ -90,6 +104,15 @@ class Engine(object): | ||||
|         context_processors += tuple(self.context_processors) | ||||
|         return tuple(import_string(path) for path in context_processors) | ||||
|  | ||||
|     def get_template_builtins(self, builtins): | ||||
|         return [import_library(x) for x in builtins] | ||||
|  | ||||
|     def get_template_libraries(self, libraries): | ||||
|         loaded = {} | ||||
|         for name, path in libraries.items(): | ||||
|             loaded[name] = import_library(path) | ||||
|         return loaded | ||||
|  | ||||
|     @cached_property | ||||
|     def template_loaders(self): | ||||
|         return self.get_template_loaders(self.loaders) | ||||
|   | ||||
							
								
								
									
										327
									
								
								django/template/library.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										327
									
								
								django/template/library.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,327 @@ | ||||
| import functools | ||||
| import warnings | ||||
| from importlib import import_module | ||||
| from inspect import getargspec | ||||
|  | ||||
| from django.utils import six | ||||
| from django.utils.deprecation import RemovedInDjango21Warning | ||||
| from django.utils.itercompat import is_iterable | ||||
|  | ||||
| from .base import Node, Template, token_kwargs | ||||
| from .exceptions import TemplateSyntaxError | ||||
|  | ||||
|  | ||||
| class InvalidTemplateLibrary(Exception): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class Library(object): | ||||
|     """ | ||||
|     A class for registering template tags and filters. Compiled filter and | ||||
|     template tag functions are stored in the filters and tags attributes. | ||||
|     The filter, simple_tag, and inclusion_tag methods provide a convenient | ||||
|     way to register callables as tags. | ||||
|     """ | ||||
|     def __init__(self): | ||||
|         self.filters = {} | ||||
|         self.tags = {} | ||||
|  | ||||
|     def tag(self, name=None, compile_function=None): | ||||
|         if name is None and compile_function is None: | ||||
|             # @register.tag() | ||||
|             return self.tag_function | ||||
|         elif name is not None and compile_function is None: | ||||
|             if callable(name): | ||||
|                 # @register.tag | ||||
|                 return self.tag_function(name) | ||||
|             else: | ||||
|                 # @register.tag('somename') or @register.tag(name='somename') | ||||
|                 def dec(func): | ||||
|                     return self.tag(name, func) | ||||
|                 return dec | ||||
|         elif name is not None and compile_function is not None: | ||||
|             # register.tag('somename', somefunc) | ||||
|             self.tags[name] = compile_function | ||||
|             return compile_function | ||||
|         else: | ||||
|             raise ValueError( | ||||
|                 "Unsupported arguments to Library.tag: (%r, %r)" % | ||||
|                 (name, compile_function), | ||||
|             ) | ||||
|  | ||||
|     def tag_function(self, func): | ||||
|         self.tags[getattr(func, "_decorated_function", func).__name__] = func | ||||
|         return func | ||||
|  | ||||
|     def filter(self, name=None, filter_func=None, **flags): | ||||
|         """ | ||||
|         Register a callable as a template filter. Example: | ||||
|  | ||||
|         @register.filter | ||||
|         def lower(value): | ||||
|             return value.lower() | ||||
|         """ | ||||
|         if name is None and filter_func is None: | ||||
|             # @register.filter() | ||||
|             def dec(func): | ||||
|                 return self.filter_function(func, **flags) | ||||
|             return dec | ||||
|         elif name is not None and filter_func is None: | ||||
|             if callable(name): | ||||
|                 # @register.filter | ||||
|                 return self.filter_function(name, **flags) | ||||
|             else: | ||||
|                 # @register.filter('somename') or @register.filter(name='somename') | ||||
|                 def dec(func): | ||||
|                     return self.filter(name, func, **flags) | ||||
|                 return dec | ||||
|         elif name is not None and filter_func is not None: | ||||
|             # register.filter('somename', somefunc) | ||||
|             self.filters[name] = filter_func | ||||
|             for attr in ('expects_localtime', 'is_safe', 'needs_autoescape'): | ||||
|                 if attr in flags: | ||||
|                     value = flags[attr] | ||||
|                     # set the flag on the filter for FilterExpression.resolve | ||||
|                     setattr(filter_func, attr, value) | ||||
|                     # set the flag on the innermost decorated function | ||||
|                     # for decorators that need it, e.g. stringfilter | ||||
|                     if hasattr(filter_func, "_decorated_function"): | ||||
|                         setattr(filter_func._decorated_function, attr, value) | ||||
|             filter_func._filter_name = name | ||||
|             return filter_func | ||||
|         else: | ||||
|             raise ValueError( | ||||
|                 "Unsupported arguments to Library.filter: (%r, %r)" % | ||||
|                 (name, filter_func), | ||||
|             ) | ||||
|  | ||||
|     def filter_function(self, func, **flags): | ||||
|         name = getattr(func, "_decorated_function", func).__name__ | ||||
|         return self.filter(name, func, **flags) | ||||
|  | ||||
|     def simple_tag(self, func=None, takes_context=None, name=None): | ||||
|         """ | ||||
|         Register a callable as a compiled template tag. Example: | ||||
|  | ||||
|         @register.simple_tag | ||||
|         def hello(*args, **kwargs): | ||||
|             return 'world' | ||||
|         """ | ||||
|         def dec(func): | ||||
|             params, varargs, varkw, defaults = getargspec(func) | ||||
|             function_name = (name or getattr(func, '_decorated_function', func).__name__) | ||||
|  | ||||
|             @functools.wraps(func) | ||||
|             def compile_func(parser, token): | ||||
|                 bits = token.split_contents()[1:] | ||||
|                 target_var = None | ||||
|                 if len(bits) >= 2 and bits[-2] == 'as': | ||||
|                     target_var = bits[-1] | ||||
|                     bits = bits[:-2] | ||||
|                 args, kwargs = parse_bits(parser, bits, params, | ||||
|                     varargs, varkw, defaults, takes_context, function_name) | ||||
|                 return SimpleNode(func, takes_context, args, kwargs, target_var) | ||||
|             self.tag(function_name, compile_func) | ||||
|             return func | ||||
|  | ||||
|         if func is None: | ||||
|             # @register.simple_tag(...) | ||||
|             return dec | ||||
|         elif callable(func): | ||||
|             # @register.simple_tag | ||||
|             return dec(func) | ||||
|         else: | ||||
|             raise ValueError("Invalid arguments provided to simple_tag") | ||||
|  | ||||
|     def assignment_tag(self, func=None, takes_context=None, name=None): | ||||
|         warnings.warn( | ||||
|             "assignment_tag() is deprecated. Use simple_tag() instead", | ||||
|             RemovedInDjango21Warning, | ||||
|             stacklevel=2, | ||||
|         ) | ||||
|         return self.simple_tag(func, takes_context, name) | ||||
|  | ||||
|     def inclusion_tag(self, filename, func=None, takes_context=None, name=None): | ||||
|         """ | ||||
|         Register a callable as an inclusion tag: | ||||
|  | ||||
|         @register.inclusion_tag('results.html') | ||||
|         def show_results(poll): | ||||
|             choices = poll.choice_set.all() | ||||
|             return {'choices': choices} | ||||
|         """ | ||||
|         def dec(func): | ||||
|             params, varargs, varkw, defaults = getargspec(func) | ||||
|             function_name = (name or getattr(func, '_decorated_function', func).__name__) | ||||
|  | ||||
|             @functools.wraps(func) | ||||
|             def compile_func(parser, token): | ||||
|                 bits = token.split_contents()[1:] | ||||
|                 args, kwargs = parse_bits( | ||||
|                     parser, bits, params, varargs, varkw, defaults, | ||||
|                     takes_context, function_name, | ||||
|                 ) | ||||
|                 return InclusionNode( | ||||
|                     func, takes_context, args, kwargs, filename, | ||||
|                 ) | ||||
|             self.tag(function_name, compile_func) | ||||
|             return func | ||||
|         return dec | ||||
|  | ||||
|  | ||||
| class TagHelperNode(Node): | ||||
|     """ | ||||
|     Base class for tag helper nodes such as SimpleNode and InclusionNode. | ||||
|     Manages the positional and keyword arguments to be passed to the decorated | ||||
|     function. | ||||
|     """ | ||||
|     def __init__(self, func, takes_context, args, kwargs): | ||||
|         self.func = func | ||||
|         self.takes_context = takes_context | ||||
|         self.args = args | ||||
|         self.kwargs = kwargs | ||||
|  | ||||
|     def get_resolved_arguments(self, context): | ||||
|         resolved_args = [var.resolve(context) for var in self.args] | ||||
|         if self.takes_context: | ||||
|             resolved_args = [context] + resolved_args | ||||
|         resolved_kwargs = {k: v.resolve(context) for k, v in self.kwargs.items()} | ||||
|         return resolved_args, resolved_kwargs | ||||
|  | ||||
|  | ||||
| class SimpleNode(TagHelperNode): | ||||
|  | ||||
|     def __init__(self, func, takes_context, args, kwargs, target_var): | ||||
|         super(SimpleNode, self).__init__(func, takes_context, args, kwargs) | ||||
|         self.target_var = target_var | ||||
|  | ||||
|     def render(self, context): | ||||
|         resolved_args, resolved_kwargs = self.get_resolved_arguments(context) | ||||
|         output = self.func(*resolved_args, **resolved_kwargs) | ||||
|         if self.target_var is not None: | ||||
|             context[self.target_var] = output | ||||
|             return '' | ||||
|         return output | ||||
|  | ||||
|  | ||||
| class InclusionNode(TagHelperNode): | ||||
|  | ||||
|     def __init__(self, func, takes_context, args, kwargs, filename): | ||||
|         super(InclusionNode, self).__init__(func, takes_context, args, kwargs) | ||||
|         self.filename = filename | ||||
|  | ||||
|     def render(self, context): | ||||
|         """ | ||||
|         Render the specified template and context. Cache the template object | ||||
|         in render_context to avoid reparsing and loading when used in a for | ||||
|         loop. | ||||
|         """ | ||||
|         resolved_args, resolved_kwargs = self.get_resolved_arguments(context) | ||||
|         _dict = self.func(*resolved_args, **resolved_kwargs) | ||||
|  | ||||
|         t = context.render_context.get(self) | ||||
|         if t is None: | ||||
|             if isinstance(self.filename, Template): | ||||
|                 t = self.filename | ||||
|             elif isinstance(getattr(self.filename, 'template', None), Template): | ||||
|                 t = self.filename.template | ||||
|             elif not isinstance(self.filename, six.string_types) and is_iterable(self.filename): | ||||
|                 t = context.template.engine.select_template(self.filename) | ||||
|             else: | ||||
|                 t = context.template.engine.get_template(self.filename) | ||||
|             context.render_context[self] = t | ||||
|         new_context = context.new(_dict) | ||||
|         # Copy across the CSRF token, if present, because inclusion tags are | ||||
|         # often used for forms, and we need instructions for using CSRF | ||||
|         # protection to be as simple as possible. | ||||
|         csrf_token = context.get('csrf_token') | ||||
|         if csrf_token is not None: | ||||
|             new_context['csrf_token'] = csrf_token | ||||
|         return t.render(new_context) | ||||
|  | ||||
|  | ||||
| def parse_bits(parser, bits, params, varargs, varkw, defaults, | ||||
|                takes_context, name): | ||||
|     """ | ||||
|     Parse bits for template tag helpers simple_tag and inclusion_tag, in | ||||
|     particular by detecting syntax errors and by extracting positional and | ||||
|     keyword arguments. | ||||
|     """ | ||||
|     if takes_context: | ||||
|         if params[0] == 'context': | ||||
|             params = params[1:] | ||||
|         else: | ||||
|             raise TemplateSyntaxError( | ||||
|                 "'%s' is decorated with takes_context=True so it must " | ||||
|                 "have a first argument of 'context'" % name) | ||||
|     args = [] | ||||
|     kwargs = {} | ||||
|     unhandled_params = list(params) | ||||
|     for bit in bits: | ||||
|         # First we try to extract a potential kwarg from the bit | ||||
|         kwarg = token_kwargs([bit], parser) | ||||
|         if kwarg: | ||||
|             # The kwarg was successfully extracted | ||||
|             param, value = kwarg.popitem() | ||||
|             if param not in params and varkw is None: | ||||
|                 # An unexpected keyword argument was supplied | ||||
|                 raise TemplateSyntaxError( | ||||
|                     "'%s' received unexpected keyword argument '%s'" % | ||||
|                     (name, param)) | ||||
|             elif param in kwargs: | ||||
|                 # The keyword argument has already been supplied once | ||||
|                 raise TemplateSyntaxError( | ||||
|                     "'%s' received multiple values for keyword argument '%s'" % | ||||
|                     (name, param)) | ||||
|             else: | ||||
|                 # All good, record the keyword argument | ||||
|                 kwargs[str(param)] = value | ||||
|                 if param in unhandled_params: | ||||
|                     # If using the keyword syntax for a positional arg, then | ||||
|                     # consume it. | ||||
|                     unhandled_params.remove(param) | ||||
|         else: | ||||
|             if kwargs: | ||||
|                 raise TemplateSyntaxError( | ||||
|                     "'%s' received some positional argument(s) after some " | ||||
|                     "keyword argument(s)" % name) | ||||
|             else: | ||||
|                 # Record the positional argument | ||||
|                 args.append(parser.compile_filter(bit)) | ||||
|                 try: | ||||
|                     # Consume from the list of expected positional arguments | ||||
|                     unhandled_params.pop(0) | ||||
|                 except IndexError: | ||||
|                     if varargs is None: | ||||
|                         raise TemplateSyntaxError( | ||||
|                             "'%s' received too many positional arguments" % | ||||
|                             name) | ||||
|     if defaults is not None: | ||||
|         # Consider the last n params handled, where n is the | ||||
|         # number of defaults. | ||||
|         unhandled_params = unhandled_params[:-len(defaults)] | ||||
|     if unhandled_params: | ||||
|         # Some positional arguments were not supplied | ||||
|         raise TemplateSyntaxError( | ||||
|             "'%s' did not receive value(s) for the argument(s): %s" % | ||||
|             (name, ", ".join("'%s'" % p for p in unhandled_params))) | ||||
|     return args, kwargs | ||||
|  | ||||
|  | ||||
| def import_library(name): | ||||
|     """ | ||||
|     Load a Library object from a template tag module. | ||||
|     """ | ||||
|     try: | ||||
|         module = import_module(name) | ||||
|     except ImportError as e: | ||||
|         raise InvalidTemplateLibrary( | ||||
|             "Invalid template library specified. ImportError raised when " | ||||
|             "trying to load '%s': %s" % (name, e) | ||||
|         ) | ||||
|     try: | ||||
|         return module.register | ||||
|     except AttributeError: | ||||
|         raise InvalidTemplateLibrary( | ||||
|             "Module  %s does not have a variable named 'register'" % name, | ||||
|         ) | ||||
| @@ -4,9 +4,9 @@ from django.utils import six | ||||
| from django.utils.safestring import mark_safe | ||||
|  | ||||
| from .base import ( | ||||
|     Library, Node, Template, TemplateSyntaxError, TextNode, Variable, | ||||
|     token_kwargs, | ||||
|     Node, Template, TemplateSyntaxError, TextNode, Variable, token_kwargs, | ||||
| ) | ||||
| from .library import Library | ||||
|  | ||||
| register = Library() | ||||
|  | ||||
|   | ||||
| @@ -35,9 +35,6 @@ def update_installed_apps(**kwargs): | ||||
|         # Rebuild management commands cache | ||||
|         from django.core.management import get_commands | ||||
|         get_commands.cache_clear() | ||||
|         # Rebuild templatetags module cache. | ||||
|         from django.template.base import get_templatetags_modules | ||||
|         get_templatetags_modules.cache_clear() | ||||
|         # Rebuild get_app_template_dirs cache. | ||||
|         from django.template.utils import get_app_template_dirs | ||||
|         get_app_template_dirs.cache_clear() | ||||
|   | ||||
| @@ -13,9 +13,11 @@ available to your templates using the :ttag:`{% load %}<load>` tag. | ||||
| Code layout | ||||
| ----------- | ||||
|  | ||||
| Custom template tags and filters must live inside a Django app. If they relate | ||||
| to an existing app it makes sense to bundle them there; otherwise, you should | ||||
| create a new app to hold them. | ||||
| The most common place to specify custom template tags and filters is inside | ||||
| a Django app. If they relate to an existing app, it makes sense to bundle them | ||||
| there; otherwise, they can be added to a new app. When a Django app is added | ||||
| to :setting:`INSTALLED_APPS`, any tags it defines in the conventional location | ||||
| described below are automatically made available to load within templates. | ||||
|  | ||||
| The app should contain a ``templatetags`` directory, at the same level as | ||||
| ``models.py``, ``views.py``, etc. If this doesn't already exist, create it - | ||||
| @@ -63,6 +65,15 @@ following:: | ||||
|  | ||||
|     register = template.Library() | ||||
|  | ||||
| .. versionadded:: 1.9 | ||||
|  | ||||
| Alternatively, template tag modules can be registered through the | ||||
| ``'libraries'`` argument to | ||||
| :class:`~django.template.backends.django.DjangoTemplates`. This is useful if | ||||
| you want to use a different label from the template tag module name when | ||||
| loading template tags. It also enables you to register tags without installing | ||||
| an application. | ||||
|  | ||||
| .. admonition:: Behind the scenes | ||||
|  | ||||
|     For a ton of examples, read the source code for Django's default filters | ||||
|   | ||||
| @@ -41,7 +41,7 @@ lower level APIs: | ||||
| Configuring an engine | ||||
| ===================== | ||||
|  | ||||
| .. class:: Engine([dirs][, app_dirs][, allowed_include_roots][, context_processors][, debug][, loaders][, string_if_invalid][, file_charset]) | ||||
| .. class:: Engine([dirs][, app_dirs][, allowed_include_roots][, context_processors][, debug][, loaders][, string_if_invalid][, file_charset][, libraries][, builtins]) | ||||
|  | ||||
|     .. versionadded:: 1.8 | ||||
|  | ||||
| @@ -114,6 +114,34 @@ Configuring an engine | ||||
|  | ||||
|       It defaults to ``'utf-8'``. | ||||
|  | ||||
|     * ``'libraries'``: A dictionary of labels and dotted Python paths of template | ||||
|       tag modules to register with the template engine. This is used to add new | ||||
|       libraries or provide alternate labels for existing ones. For example:: | ||||
|  | ||||
|           Engine( | ||||
|               libraries={ | ||||
|                   'myapp_tags': 'path.to.myapp.tags', | ||||
|                   'admin.urls': 'django.contrib.admin.templatetags.admin_urls', | ||||
|               }, | ||||
|           ) | ||||
|  | ||||
|       Libraries can be loaded by passing the corresponding dictionary key to | ||||
|       the :ttag:`{% load %}<load>` tag. | ||||
|  | ||||
|     * ``'builtins'``: A list of dotted Python paths of template tag modules to | ||||
|       add to :doc:`built-ins </ref/templates/builtins>`. For example:: | ||||
|  | ||||
|           Engine( | ||||
|               builtins=['myapp.builtins'], | ||||
|           ) | ||||
|  | ||||
|       Tags and filters from built-in libraries can be used without first calling | ||||
|       the :ttag:`{% load %}<load>` tag. | ||||
|  | ||||
| .. versionadded:: 1.9 | ||||
|  | ||||
|     The ``libraries`` and ``builtins`` arguments were added. | ||||
|  | ||||
| .. staticmethod:: Engine.get_default() | ||||
|  | ||||
|     When a Django project configures one and only one | ||||
|   | ||||
| @@ -263,6 +263,10 @@ Templates | ||||
| * :ref:`Debug page integration <template-debug-integration>` for custom | ||||
|   template engines was added. | ||||
|  | ||||
| * The :class:`~django.template.backends.django.DjangoTemplates` backend gained | ||||
|   the ability to register libraries and builtins explicitly through the | ||||
|   template :setting:`OPTIONS <TEMPLATES-OPTIONS>`. | ||||
|  | ||||
| Requests and Responses | ||||
| ^^^^^^^^^^^^^^^^^^^^^^ | ||||
|  | ||||
| @@ -467,6 +471,28 @@ You don't need any of this if you're querying the database through the ORM, | ||||
| even if you're using :meth:`raw() <django.db.models.query.QuerySet.raw>` | ||||
| queries. The ORM takes care of managing time zone information. | ||||
|  | ||||
| Template tag modules are imported when templates are configured | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| The :class:`~django.template.backends.django.DjangoTemplates` backend now | ||||
| performs discovery on installed template tag modules when instantiated. This | ||||
| update enables libraries to be provided explicitly via the ``'libraries'`` | ||||
| key of :setting:`OPTIONS <TEMPLATES-OPTIONS>` when defining a | ||||
| :class:`~django.template.backends.django.DjangoTemplates` backend. Import | ||||
| or syntax errors in template tag modules now fail early at instantiation time | ||||
| rather than when a template with a :ttag:`{% load %}<load>` tag is first | ||||
| compiled. | ||||
|  | ||||
| ``django.template.base.add_to_builtins()`` is removed | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
|  | ||||
| Although it was a private API, projects commonly used ``add_to_builtins()`` to | ||||
| make template tags and filters available without using the | ||||
| :ttag:`{% load %}<load>` tag. This API has been formalized. Projects should now | ||||
| define built-in libraries via the ``'builtins'`` key of :setting:`OPTIONS | ||||
| <TEMPLATES-OPTIONS>` when defining a | ||||
| :class:`~django.template.backends.django.DjangoTemplates` backend. | ||||
|  | ||||
| Miscellaneous | ||||
| ~~~~~~~~~~~~~ | ||||
|  | ||||
|   | ||||
| @@ -401,6 +401,34 @@ applications. This generic name was kept for backwards-compatibility. | ||||
|  | ||||
|   It defaults to the value of :setting:`FILE_CHARSET`. | ||||
|  | ||||
| * ``'libraries'``: A dictionary of labels and dotted Python paths of template | ||||
|   tag modules to register with the template engine. This can be used to add | ||||
|   new libraries or provide alternate labels for existing ones. For example:: | ||||
|  | ||||
|       OPTIONS={ | ||||
|           'libraries': { | ||||
|               'myapp_tags': 'path.to.myapp.tags', | ||||
|               'admin.urls': 'django.contrib.admin.templatetags.admin_urls', | ||||
|           }, | ||||
|       } | ||||
|  | ||||
|   Libraries can be loaded by passing the corresponding dictionary key to | ||||
|   the :ttag:`{% load %}<load>` tag. | ||||
|  | ||||
| * ``'builtins'``: A list of dotted Python paths of template tag modules to | ||||
|   add to :doc:`built-ins </ref/templates/builtins>`. For example:: | ||||
|  | ||||
|       OPTIONS={ | ||||
|           'builtins': ['myapp.builtins'], | ||||
|       } | ||||
|  | ||||
|   Tags and filters from built-in libraries can be used without first calling | ||||
|   the :ttag:`{% load %} <load>` tag. | ||||
|  | ||||
| .. versionadded:: 1.9 | ||||
|  | ||||
|     The ``libraries`` and ``builtins`` arguments were added. | ||||
|  | ||||
| .. module:: django.template.backends.jinja2 | ||||
|  | ||||
| .. class:: Jinja2 | ||||
|   | ||||
							
								
								
									
										0
									
								
								tests/template_backends/apps/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/template_backends/apps/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										0
									
								
								tests/template_backends/apps/good/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/template_backends/apps/good/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| from django.template import Library | ||||
|  | ||||
| register = Library() | ||||
| @@ -0,0 +1,3 @@ | ||||
| from django.template import Library | ||||
|  | ||||
| register = Library() | ||||
| @@ -0,0 +1,3 @@ | ||||
| from django.template import Library | ||||
|  | ||||
| register = Library() | ||||
| @@ -0,0 +1 @@ | ||||
| import DoesNotExist  # noqa | ||||
| @@ -2,7 +2,8 @@ from template_tests.test_response import test_processor_name | ||||
|  | ||||
| from django.template import RequestContext | ||||
| from django.template.backends.django import DjangoTemplates | ||||
| from django.test import RequestFactory, ignore_warnings | ||||
| from django.template.library import InvalidTemplateLibrary | ||||
| from django.test import RequestFactory, ignore_warnings, override_settings | ||||
| from django.utils.deprecation import RemovedInDjango20Warning | ||||
|  | ||||
| from .test_dummy import TemplateStringsTests | ||||
| @@ -51,3 +52,78 @@ class DjangoTemplatesTests(TemplateStringsTests): | ||||
|                "the two arguments refer to the same request.") | ||||
|         with self.assertRaisesMessage(ValueError, msg): | ||||
|             template.render(request_context, other_request) | ||||
|  | ||||
|     @override_settings(INSTALLED_APPS=['template_backends.apps.good']) | ||||
|     def test_templatetag_discovery(self): | ||||
|         engine = DjangoTemplates({ | ||||
|             'DIRS': [], | ||||
|             'APP_DIRS': False, | ||||
|             'NAME': 'django', | ||||
|             'OPTIONS': { | ||||
|                 'libraries': { | ||||
|                     'alternate': 'template_backends.apps.good.templatetags.good_tags', | ||||
|                     'override': 'template_backends.apps.good.templatetags.good_tags', | ||||
|                 }, | ||||
|             }, | ||||
|         }) | ||||
|  | ||||
|         # libraries are discovered from installed applications | ||||
|         self.assertEqual( | ||||
|             engine.engine.libraries['good_tags'], | ||||
|             'template_backends.apps.good.templatetags.good_tags', | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             engine.engine.libraries['subpackage.tags'], | ||||
|             'template_backends.apps.good.templatetags.subpackage.tags', | ||||
|         ) | ||||
|         # libraries are discovered from django.templatetags | ||||
|         self.assertEqual( | ||||
|             engine.engine.libraries['static'], | ||||
|             'django.templatetags.static', | ||||
|         ) | ||||
|         # libraries passed in OPTIONS are registered | ||||
|         self.assertEqual( | ||||
|             engine.engine.libraries['alternate'], | ||||
|             'template_backends.apps.good.templatetags.good_tags', | ||||
|         ) | ||||
|         # libraries passed in OPTIONS take precedence over discovered ones | ||||
|         self.assertEqual( | ||||
|             engine.engine.libraries['override'], | ||||
|             'template_backends.apps.good.templatetags.good_tags', | ||||
|         ) | ||||
|  | ||||
|     @override_settings(INSTALLED_APPS=['template_backends.apps.importerror']) | ||||
|     def test_templatetag_discovery_import_error(self): | ||||
|         """ | ||||
|         Import errors in tag modules should be reraised with a helpful message. | ||||
|         """ | ||||
|         with self.assertRaisesMessage( | ||||
|             InvalidTemplateLibrary, | ||||
|             "ImportError raised when trying to load " | ||||
|             "'template_backends.apps.importerror.templatetags.broken_tags'" | ||||
|         ): | ||||
|             DjangoTemplates({ | ||||
|                 'DIRS': [], | ||||
|                 'APP_DIRS': False, | ||||
|                 'NAME': 'django', | ||||
|                 'OPTIONS': {}, | ||||
|             }) | ||||
|  | ||||
|     def test_builtins_discovery(self): | ||||
|         engine = DjangoTemplates({ | ||||
|             'DIRS': [], | ||||
|             'APP_DIRS': False, | ||||
|             'NAME': 'django', | ||||
|             'OPTIONS': { | ||||
|                 'builtins': ['template_backends.apps.good.templatetags.good_tags'], | ||||
|             }, | ||||
|         }) | ||||
|  | ||||
|         self.assertEqual( | ||||
|             engine.engine.builtins, [ | ||||
|                 'django.template.defaulttags', | ||||
|                 'django.template.defaultfilters', | ||||
|                 'django.template.loader_tags', | ||||
|                 'template_backends.apps.good.templatetags.good_tags', | ||||
|             ] | ||||
|         ) | ||||
|   | ||||
| @@ -6,6 +6,10 @@ from ..utils import setup | ||||
|  | ||||
|  | ||||
| class CacheTagTests(SimpleTestCase): | ||||
|     libraries = { | ||||
|         'cache': 'django.templatetags.cache', | ||||
|         'custom': 'template_tests.templatetags.custom', | ||||
|     } | ||||
|  | ||||
|     def tearDown(self): | ||||
|         cache.clear() | ||||
| @@ -121,7 +125,7 @@ class CacheTests(SimpleTestCase): | ||||
|  | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         cls.engine = Engine() | ||||
|         cls.engine = Engine(libraries={'cache': 'django.templatetags.cache'}) | ||||
|         super(CacheTests, cls).setUpClass() | ||||
|  | ||||
|     def test_cache_regression_20130(self): | ||||
|   | ||||
| @@ -6,6 +6,7 @@ from ..utils import setup | ||||
|  | ||||
|  | ||||
| class CycleTagTests(SimpleTestCase): | ||||
|     libraries = {'future': 'django.templatetags.future'} | ||||
|  | ||||
|     @setup({'cycle01': '{% cycle a %}'}) | ||||
|     def test_cycle01(self): | ||||
|   | ||||
| @@ -56,6 +56,7 @@ inheritance_templates = { | ||||
|  | ||||
|  | ||||
| class InheritanceTests(SimpleTestCase): | ||||
|     libraries = {'testtags': 'template_tests.templatetags.testtags'} | ||||
|  | ||||
|     @setup(inheritance_templates) | ||||
|     def test_inheritance01(self): | ||||
|   | ||||
| @@ -6,6 +6,7 @@ from ..utils import setup | ||||
|  | ||||
|  | ||||
| class FirstOfTagTests(SimpleTestCase): | ||||
|     libraries = {'future': 'django.templatetags.future'} | ||||
|  | ||||
|     @setup({'firstof01': '{% firstof a b c %}'}) | ||||
|     def test_firstof01(self): | ||||
|   | ||||
| @@ -6,6 +6,7 @@ from ..utils import setup | ||||
|  | ||||
|  | ||||
| class ForTagTests(SimpleTestCase): | ||||
|     libraries = {'custom': 'template_tests.templatetags.custom'} | ||||
|  | ||||
|     @setup({'for-tag01': '{% for val in values %}{{ val }}{% endfor %}'}) | ||||
|     def test_for_tag01(self): | ||||
|   | ||||
| @@ -10,6 +10,10 @@ from ..utils import setup | ||||
|  | ||||
|  | ||||
| class I18nTagTests(SimpleTestCase): | ||||
|     libraries = { | ||||
|         'custom': 'template_tests.templatetags.custom', | ||||
|         'i18n': 'django.templatetags.i18n', | ||||
|     } | ||||
|  | ||||
|     @setup({'i18n01': '{% load i18n %}{% trans \'xxxyyyxxx\' %}'}) | ||||
|     def test_i18n01(self): | ||||
|   | ||||
| @@ -5,6 +5,7 @@ from ..utils import setup | ||||
|  | ||||
|  | ||||
| class IfChangedTagTests(SimpleTestCase): | ||||
|     libraries = {'custom': 'template_tests.templatetags.custom'} | ||||
|  | ||||
|     @setup({'ifchanged01': '{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}'}) | ||||
|     def test_ifchanged01(self): | ||||
|   | ||||
| @@ -13,6 +13,7 @@ include_fail_templates = { | ||||
|  | ||||
|  | ||||
| class IncludeTagTests(SimpleTestCase): | ||||
|     libraries = {'bad_tag': 'template_tests.templatetags.bad_tag'} | ||||
|  | ||||
|     @setup({'include01': '{% include "basic-syntax01" %}'}, basic_templates) | ||||
|     def test_include01(self): | ||||
|   | ||||
| @@ -4,6 +4,7 @@ from ..utils import setup | ||||
|  | ||||
|  | ||||
| class InvalidStringTests(SimpleTestCase): | ||||
|     libraries = {'i18n': 'django.templatetags.i18n'} | ||||
|  | ||||
|     @setup({'invalidstr01': '{{ var|default:"Foo" }}'}) | ||||
|     def test_invalidstr01(self): | ||||
|   | ||||
| @@ -5,6 +5,10 @@ from ..utils import setup | ||||
|  | ||||
|  | ||||
| class LoadTagTests(SimpleTestCase): | ||||
|     libraries = { | ||||
|         'subpackage.echo': 'template_tests.templatetags.subpackage.echo', | ||||
|         'testtags': 'template_tests.templatetags.testtags', | ||||
|     } | ||||
|  | ||||
|     @setup({'load01': '{% load testtags subpackage.echo %}{% echo test %} {% echo2 "test" %}'}) | ||||
|     def test_load01(self): | ||||
| @@ -42,30 +46,30 @@ class LoadTagTests(SimpleTestCase): | ||||
|     # {% load %} tag errors | ||||
|     @setup({'load07': '{% load echo other_echo bad_tag from testtags %}'}) | ||||
|     def test_load07(self): | ||||
|         with self.assertRaises(TemplateSyntaxError): | ||||
|         msg = "'bad_tag' is not a valid tag or filter in tag library 'testtags'" | ||||
|         with self.assertRaisesMessage(TemplateSyntaxError, msg): | ||||
|             self.engine.get_template('load07') | ||||
|  | ||||
|     @setup({'load08': '{% load echo other_echo bad_tag from %}'}) | ||||
|     def test_load08(self): | ||||
|         with self.assertRaises(TemplateSyntaxError): | ||||
|         msg = "'echo' is not a registered tag library. Must be one of:\nsubpackage.echo\ntesttags" | ||||
|         with self.assertRaisesMessage(TemplateSyntaxError, msg): | ||||
|             self.engine.get_template('load08') | ||||
|  | ||||
|     @setup({'load09': '{% load from testtags %}'}) | ||||
|     def test_load09(self): | ||||
|         with self.assertRaises(TemplateSyntaxError): | ||||
|         msg = "'from' is not a registered tag library. Must be one of:\nsubpackage.echo\ntesttags" | ||||
|         with self.assertRaisesMessage(TemplateSyntaxError, msg): | ||||
|             self.engine.get_template('load09') | ||||
|  | ||||
|     @setup({'load10': '{% load echo from bad_library %}'}) | ||||
|     def test_load10(self): | ||||
|         with self.assertRaises(TemplateSyntaxError): | ||||
|         msg = "'bad_library' is not a registered tag library. Must be one of:\nsubpackage.echo\ntesttags" | ||||
|         with self.assertRaisesMessage(TemplateSyntaxError, msg): | ||||
|             self.engine.get_template('load10') | ||||
|  | ||||
|     @setup({'load11': '{% load subpackage.echo_invalid %}'}) | ||||
|     def test_load11(self): | ||||
|         with self.assertRaises(TemplateSyntaxError): | ||||
|             self.engine.get_template('load11') | ||||
|  | ||||
|     @setup({'load12': '{% load subpackage.missing %}'}) | ||||
|     def test_load12(self): | ||||
|         with self.assertRaises(TemplateSyntaxError): | ||||
|         msg = "'subpackage.missing' is not a registered tag library. Must be one of:\nsubpackage.echo\ntesttags" | ||||
|         with self.assertRaisesMessage(TemplateSyntaxError, msg): | ||||
|             self.engine.get_template('load12') | ||||
|   | ||||
| @@ -5,6 +5,7 @@ from ..utils import setup | ||||
|  | ||||
|  | ||||
| class SimpleTagTests(SimpleTestCase): | ||||
|     libraries = {'custom': 'template_tests.templatetags.custom'} | ||||
|  | ||||
|     @setup({'simpletag-renamed01': '{% load custom %}{% minusone 7 %}'}) | ||||
|     def test_simpletag_renamed01(self): | ||||
|   | ||||
| @@ -7,6 +7,7 @@ from ..utils import setup | ||||
|  | ||||
| @override_settings(MEDIA_URL="/media/", STATIC_URL="/static/") | ||||
| class StaticTagTests(SimpleTestCase): | ||||
|     libraries = {'static': 'django.templatetags.static'} | ||||
|  | ||||
|     @setup({'static-prefixtag01': '{% load static %}{% get_static_prefix %}'}) | ||||
|     def test_static_prefixtag01(self): | ||||
|   | ||||
| @@ -6,6 +6,7 @@ from ..utils import setup | ||||
|  | ||||
|  | ||||
| class WidthRatioTagTests(SimpleTestCase): | ||||
|     libraries = {'custom': 'template_tests.templatetags.custom'} | ||||
|  | ||||
|     @setup({'widthratio01': '{% widthratio a b 0 %}'}) | ||||
|     def test_widthratio01(self): | ||||
|   | ||||
| @@ -1 +0,0 @@ | ||||
| import nonexistent.module  # NOQA | ||||
							
								
								
									
										22
									
								
								tests/template_tests/templatetags/testtags.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								tests/template_tests/templatetags/testtags.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| from django.template import Library, Node | ||||
|  | ||||
| register = Library() | ||||
|  | ||||
|  | ||||
| class EchoNode(Node): | ||||
|     def __init__(self, contents): | ||||
|         self.contents = contents | ||||
|  | ||||
|     def render(self, context): | ||||
|         return ' '.join(self.contents) | ||||
|  | ||||
|  | ||||
| @register.tag | ||||
| def echo(parser, token): | ||||
|     return EchoNode(token.contents.split()[1:]) | ||||
| register.tag('other_echo', echo) | ||||
|  | ||||
|  | ||||
| @register.filter | ||||
| def upper(value): | ||||
|     return value.upper() | ||||
| @@ -4,18 +4,26 @@ import os | ||||
|  | ||||
| from django.template import Context, Engine, TemplateSyntaxError | ||||
| from django.template.base import Node | ||||
| from django.template.library import InvalidTemplateLibrary | ||||
| from django.test import SimpleTestCase, ignore_warnings | ||||
| from django.test.utils import extend_sys_path | ||||
| from django.utils import six | ||||
| from django.utils.deprecation import RemovedInDjango20Warning | ||||
|  | ||||
| from .templatetags import custom, inclusion | ||||
| from .utils import ROOT | ||||
|  | ||||
| LIBRARIES = { | ||||
|     'custom': 'template_tests.templatetags.custom', | ||||
|     'inclusion': 'template_tests.templatetags.inclusion', | ||||
| } | ||||
|  | ||||
|  | ||||
| class CustomFilterTests(SimpleTestCase): | ||||
|  | ||||
|     def test_filter(self): | ||||
|         t = Engine().from_string("{% load custom %}{{ string|trim:5 }}") | ||||
|         engine = Engine(libraries=LIBRARIES) | ||||
|         t = engine.from_string("{% load custom %}{{ string|trim:5 }}") | ||||
|         self.assertEqual( | ||||
|             t.render(Context({"string": "abcdefghijklmnopqrstuvwxyz"})), | ||||
|             "abcde" | ||||
| @@ -26,7 +34,7 @@ class TagTestCase(SimpleTestCase): | ||||
|  | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         cls.engine = Engine(app_dirs=True) | ||||
|         cls.engine = Engine(app_dirs=True, libraries=LIBRARIES) | ||||
|         super(TagTestCase, cls).setUpClass() | ||||
|  | ||||
|     def verify_tag(self, tag, name): | ||||
| @@ -269,7 +277,7 @@ class InclusionTagTests(TagTestCase): | ||||
|         """ | ||||
|         #23441 -- InclusionNode shouldn't modify its nodelist at render time. | ||||
|         """ | ||||
|         engine = Engine(app_dirs=True) | ||||
|         engine = Engine(app_dirs=True, libraries=LIBRARIES) | ||||
|         template = engine.from_string('{% load inclusion %}{% inclusion_no_params %}') | ||||
|         count = template.nodelist.get_nodes_by_type(Node) | ||||
|         template.render(Context({})) | ||||
| @@ -281,7 +289,7 @@ class InclusionTagTests(TagTestCase): | ||||
|         when rendering. Otherwise, leftover values such as blocks from | ||||
|         extending can interfere with subsequent rendering. | ||||
|         """ | ||||
|         engine = Engine(app_dirs=True) | ||||
|         engine = Engine(app_dirs=True, libraries=LIBRARIES) | ||||
|         template = engine.from_string('{% load inclusion %}{% inclusion_extends1 %}{% inclusion_extends2 %}') | ||||
|         self.assertEqual(template.render(Context({})).strip(), 'one\ntwo') | ||||
|  | ||||
| @@ -313,34 +321,37 @@ class TemplateTagLoadingTests(SimpleTestCase): | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         cls.egg_dir = os.path.join(ROOT, 'eggs') | ||||
|         cls.engine = Engine() | ||||
|         super(TemplateTagLoadingTests, cls).setUpClass() | ||||
|  | ||||
|     def test_load_error(self): | ||||
|         ttext = "{% load broken_tag %}" | ||||
|         with self.assertRaises(TemplateSyntaxError) as e: | ||||
|             self.engine.from_string(ttext) | ||||
|  | ||||
|         self.assertIn('ImportError', e.exception.args[0]) | ||||
|         self.assertIn('Xtemplate', e.exception.args[0]) | ||||
|         msg = ( | ||||
|             "Invalid template library specified. ImportError raised when " | ||||
|             "trying to load 'template_tests.broken_tag': cannot import name " | ||||
|             "'?Xtemplate'?" | ||||
|         ) | ||||
|         with six.assertRaisesRegex(self, InvalidTemplateLibrary, msg): | ||||
|             Engine(libraries={ | ||||
|                 'broken_tag': 'template_tests.broken_tag', | ||||
|             }) | ||||
|  | ||||
|     def test_load_error_egg(self): | ||||
|         ttext = "{% load broken_egg %}" | ||||
|         egg_name = '%s/tagsegg.egg' % self.egg_dir | ||||
|         msg = ( | ||||
|             "Invalid template library specified. ImportError raised when " | ||||
|             "trying to load 'tagsegg.templatetags.broken_egg': cannot " | ||||
|             "import name '?Xtemplate'?" | ||||
|         ) | ||||
|         with extend_sys_path(egg_name): | ||||
|             with self.assertRaises(TemplateSyntaxError): | ||||
|                 with self.settings(INSTALLED_APPS=['tagsegg']): | ||||
|                     self.engine.from_string(ttext) | ||||
|             try: | ||||
|                 with self.settings(INSTALLED_APPS=['tagsegg']): | ||||
|                     self.engine.from_string(ttext) | ||||
|             except TemplateSyntaxError as e: | ||||
|                 self.assertIn('ImportError', e.args[0]) | ||||
|                 self.assertIn('Xtemplate', e.args[0]) | ||||
|             with six.assertRaisesRegex(self, InvalidTemplateLibrary, msg): | ||||
|                 Engine(libraries={ | ||||
|                     'broken_egg': 'tagsegg.templatetags.broken_egg', | ||||
|                 }) | ||||
|  | ||||
|     def test_load_working_egg(self): | ||||
|         ttext = "{% load working_egg %}" | ||||
|         egg_name = '%s/tagsegg.egg' % self.egg_dir | ||||
|         with extend_sys_path(egg_name): | ||||
|             with self.settings(INSTALLED_APPS=['tagsegg']): | ||||
|                 self.engine.from_string(ttext) | ||||
|             engine = Engine(libraries={ | ||||
|                 'working_egg': 'tagsegg.templatetags.working_egg', | ||||
|             }) | ||||
|             engine.from_string(ttext) | ||||
|   | ||||
| @@ -14,7 +14,10 @@ OTHER_DIR = os.path.join(ROOT, 'other_templates') | ||||
| class DeprecatedRenderToStringTest(SimpleTestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.engine = Engine(dirs=[TEMPLATE_DIR]) | ||||
|         self.engine = Engine( | ||||
|             dirs=[TEMPLATE_DIR], | ||||
|             libraries={'custom': 'template_tests.templatetags.custom'}, | ||||
|         ) | ||||
|  | ||||
|     def test_basic_context(self): | ||||
|         self.assertEqual( | ||||
|   | ||||
							
								
								
									
										132
									
								
								tests/template_tests/test_library.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								tests/template_tests/test_library.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | ||||
| from django.template import Library | ||||
| from django.template.base import Node | ||||
| from django.test import TestCase | ||||
|  | ||||
|  | ||||
| class FilterRegistrationTests(TestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.library = Library() | ||||
|  | ||||
|     def test_filter(self): | ||||
|         @self.library.filter | ||||
|         def func(): | ||||
|             return '' | ||||
|         self.assertEqual(self.library.filters['func'], func) | ||||
|  | ||||
|     def test_filter_parens(self): | ||||
|         @self.library.filter() | ||||
|         def func(): | ||||
|             return '' | ||||
|         self.assertEqual(self.library.filters['func'], func) | ||||
|  | ||||
|     def test_filter_name_arg(self): | ||||
|         @self.library.filter('name') | ||||
|         def func(): | ||||
|             return '' | ||||
|         self.assertEqual(self.library.filters['name'], func) | ||||
|  | ||||
|     def test_filter_name_kwarg(self): | ||||
|         @self.library.filter(name='name') | ||||
|         def func(): | ||||
|             return '' | ||||
|         self.assertEqual(self.library.filters['name'], func) | ||||
|  | ||||
|     def test_filter_call(self): | ||||
|         def func(): | ||||
|             return '' | ||||
|         self.library.filter('name', func) | ||||
|         self.assertEqual(self.library.filters['name'], func) | ||||
|  | ||||
|     def test_filter_invalid(self): | ||||
|         msg = "Unsupported arguments to Library.filter: (None, '')" | ||||
|         with self.assertRaisesMessage(ValueError, msg): | ||||
|             self.library.filter(None, '') | ||||
|  | ||||
|  | ||||
| class InclusionTagRegistrationTests(TestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.library = Library() | ||||
|  | ||||
|     def test_inclusion_tag(self): | ||||
|         @self.library.inclusion_tag('template.html') | ||||
|         def func(): | ||||
|             return '' | ||||
|         self.assertIn('func', self.library.tags) | ||||
|  | ||||
|     def test_inclusion_tag_name(self): | ||||
|         @self.library.inclusion_tag('template.html', name='name') | ||||
|         def func(): | ||||
|             return '' | ||||
|         self.assertIn('name', self.library.tags) | ||||
|  | ||||
|  | ||||
| class SimpleTagRegistrationTests(TestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.library = Library() | ||||
|  | ||||
|     def test_simple_tag(self): | ||||
|         @self.library.simple_tag | ||||
|         def func(): | ||||
|             return '' | ||||
|         self.assertIn('func', self.library.tags) | ||||
|  | ||||
|     def test_simple_tag_parens(self): | ||||
|         @self.library.simple_tag() | ||||
|         def func(): | ||||
|             return '' | ||||
|         self.assertIn('func', self.library.tags) | ||||
|  | ||||
|     def test_simple_tag_name_kwarg(self): | ||||
|         @self.library.simple_tag(name='name') | ||||
|         def func(): | ||||
|             return '' | ||||
|         self.assertIn('name', self.library.tags) | ||||
|  | ||||
|     def test_simple_tag_invalid(self): | ||||
|         msg = "Invalid arguments provided to simple_tag" | ||||
|         with self.assertRaisesMessage(ValueError, msg): | ||||
|             self.library.simple_tag('invalid') | ||||
|  | ||||
|  | ||||
| class TagRegistrationTests(TestCase): | ||||
|  | ||||
|     def setUp(self): | ||||
|         self.library = Library() | ||||
|  | ||||
|     def test_tag(self): | ||||
|         @self.library.tag | ||||
|         def func(parser, token): | ||||
|             return Node() | ||||
|         self.assertEqual(self.library.tags['func'], func) | ||||
|  | ||||
|     def test_tag_parens(self): | ||||
|         @self.library.tag() | ||||
|         def func(parser, token): | ||||
|             return Node() | ||||
|         self.assertEqual(self.library.tags['func'], func) | ||||
|  | ||||
|     def test_tag_name_arg(self): | ||||
|         @self.library.tag('name') | ||||
|         def func(parser, token): | ||||
|             return Node() | ||||
|         self.assertEqual(self.library.tags['name'], func) | ||||
|  | ||||
|     def test_tag_name_kwarg(self): | ||||
|         @self.library.tag(name='name') | ||||
|         def func(parser, token): | ||||
|             return Node() | ||||
|         self.assertEqual(self.library.tags['name'], func) | ||||
|  | ||||
|     def test_tag_call(self): | ||||
|         def func(parser, token): | ||||
|             return Node() | ||||
|         self.library.tag('name', func) | ||||
|         self.assertEqual(self.library.tags['name'], func) | ||||
|  | ||||
|     def test_tag_invalid(self): | ||||
|         msg = "Unsupported arguments to Library.tag: (None, '')" | ||||
|         with self.assertRaisesMessage(ValueError, msg): | ||||
|             self.library.tag(None, '') | ||||
| @@ -49,7 +49,7 @@ class ErrorIndexTest(TestCase): | ||||
|             'range': range(5), | ||||
|             'five': 5, | ||||
|         }) | ||||
|         engine = Engine(debug=True) | ||||
|         engine = Engine(debug=True, libraries={'bad_tag': 'template_tests.templatetags.bad_tag'}) | ||||
|         for source, expected_error_source_index in tests: | ||||
|             template = engine.from_string(source) | ||||
|             try: | ||||
|   | ||||
| @@ -9,6 +9,7 @@ from django.template import Library, TemplateSyntaxError | ||||
| from django.template.base import ( | ||||
|     TOKEN_BLOCK, FilterExpression, Parser, Token, Variable, | ||||
| ) | ||||
| from django.template.defaultfilters import register as filter_library | ||||
| from django.utils import six | ||||
|  | ||||
|  | ||||
| @@ -24,7 +25,7 @@ class ParserTests(TestCase): | ||||
|  | ||||
|     def test_filter_parsing(self): | ||||
|         c = {"article": {"section": "News"}} | ||||
|         p = Parser("") | ||||
|         p = Parser("", builtins=[filter_library]) | ||||
|  | ||||
|         def fe_test(s, val): | ||||
|             self.assertEqual(FilterExpression(s, p).resolve(c), val) | ||||
|   | ||||
| @@ -97,7 +97,10 @@ class TemplateTests(SimpleTestCase): | ||||
|         Errors raised while compiling nodes should include the token | ||||
|         information. | ||||
|         """ | ||||
|         engine = Engine(debug=True) | ||||
|         engine = Engine( | ||||
|             debug=True, | ||||
|             libraries={'bad_tag': 'template_tests.templatetags.bad_tag'}, | ||||
|         ) | ||||
|         with self.assertRaises(RuntimeError) as e: | ||||
|             engine.from_string("{% load bad_tag %}{% badtag %}") | ||||
|         self.assertEqual(e.exception.template_debug['during'], '{% badtag %}') | ||||
|   | ||||
| @@ -5,9 +5,6 @@ from __future__ import unicode_literals | ||||
| import functools | ||||
| import os | ||||
|  | ||||
| from django import template | ||||
| from django.template import Library | ||||
| from django.template.base import libraries | ||||
| from django.template.engine import Engine | ||||
| from django.test.utils import override_settings | ||||
| from django.utils._os import upath | ||||
| @@ -49,14 +46,17 @@ def setup(templates, *args, **kwargs): | ||||
|     ] | ||||
|  | ||||
|     def decorator(func): | ||||
|         @register_test_tags | ||||
|         # Make Engine.get_default() raise an exception to ensure that tests | ||||
|         # are properly isolated from Django's global settings. | ||||
|         @override_settings(TEMPLATES=None) | ||||
|         @functools.wraps(func) | ||||
|         def inner(self): | ||||
|             # Set up custom template tag libraries if specified | ||||
|             libraries = getattr(self, 'libraries', {}) | ||||
|  | ||||
|             self.engine = Engine( | ||||
|                 allowed_include_roots=[ROOT], | ||||
|                 libraries=libraries, | ||||
|                 loaders=loaders, | ||||
|             ) | ||||
|             func(self) | ||||
| @@ -66,6 +66,7 @@ def setup(templates, *args, **kwargs): | ||||
|  | ||||
|             self.engine = Engine( | ||||
|                 allowed_include_roots=[ROOT], | ||||
|                 libraries=libraries, | ||||
|                 loaders=loaders, | ||||
|                 string_if_invalid='INVALID', | ||||
|             ) | ||||
| @@ -75,6 +76,7 @@ def setup(templates, *args, **kwargs): | ||||
|             self.engine = Engine( | ||||
|                 allowed_include_roots=[ROOT], | ||||
|                 debug=True, | ||||
|                 libraries=libraries, | ||||
|                 loaders=loaders, | ||||
|             ) | ||||
|             func(self) | ||||
| @@ -85,43 +87,9 @@ def setup(templates, *args, **kwargs): | ||||
|     return decorator | ||||
|  | ||||
|  | ||||
| # Custom template tag for tests | ||||
|  | ||||
| register = Library() | ||||
|  | ||||
|  | ||||
| class EchoNode(template.Node): | ||||
|     def __init__(self, contents): | ||||
|         self.contents = contents | ||||
|  | ||||
|     def render(self, context): | ||||
|         return ' '.join(self.contents) | ||||
|  | ||||
|  | ||||
| @register.tag | ||||
| def echo(parser, token): | ||||
|     return EchoNode(token.contents.split()[1:]) | ||||
| register.tag('other_echo', echo) | ||||
|  | ||||
|  | ||||
| @register.filter | ||||
| def upper(value): | ||||
|     return value.upper() | ||||
|  | ||||
|  | ||||
| def register_test_tags(func): | ||||
|     @functools.wraps(func) | ||||
|     def inner(self): | ||||
|         libraries['testtags'] = register | ||||
|         try: | ||||
|             func(self) | ||||
|         finally: | ||||
|             del libraries['testtags'] | ||||
|     return inner | ||||
|  | ||||
|  | ||||
| # Helper objects | ||||
|  | ||||
|  | ||||
| class SomeException(Exception): | ||||
|     silent_variable_failure = True | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user