* 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:
parent
7b8008a078
commit
655f524915
|
@ -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,31 +55,32 @@ 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]
|
||||
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__)
|
||||
if title:
|
||||
title = utils.parse_rst(title, 'tag', _('tag:') + tag_name)
|
||||
if body:
|
||||
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:
|
||||
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__)
|
||||
if title:
|
||||
title = utils.parse_rst(title, 'tag', _('tag:') + tag_name)
|
||||
if body:
|
||||
body = utils.parse_rst(body, 'tag', _('tag:') + tag_name)
|
||||
for key in metadata:
|
||||
metadata[key] = utils.parse_rst(metadata[key], 'tag', _('tag:') + tag_name)
|
||||
tag_library = module_name.split('.')[-1]
|
||||
tags.append({
|
||||
'name': tag_name,
|
||||
'title': title,
|
||||
'body': body,
|
||||
'meta': metadata,
|
||||
'library': tag_library,
|
||||
})
|
||||
tags.append({
|
||||
'name': tag_name,
|
||||
'title': title,
|
||||
'body': body,
|
||||
'meta': metadata,
|
||||
'library': tag_library,
|
||||
})
|
||||
kwargs.update({'tags': tags})
|
||||
return super(TemplateTagIndexView, self).get_context_data(**kwargs)
|
||||
|
||||
|
@ -93,31 +89,32 @@ 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]
|
||||
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__)
|
||||
if title:
|
||||
title = utils.parse_rst(title, 'filter', _('filter:') + filter_name)
|
||||
if body:
|
||||
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:
|
||||
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__)
|
||||
if title:
|
||||
title = utils.parse_rst(title, 'filter', _('filter:') + filter_name)
|
||||
if body:
|
||||
body = utils.parse_rst(body, 'filter', _('filter:') + filter_name)
|
||||
for key in metadata:
|
||||
metadata[key] = utils.parse_rst(metadata[key], 'filter', _('filter:') + filter_name)
|
||||
tag_library = module_name.split('.')[-1]
|
||||
filters.append({
|
||||
'name': filter_name,
|
||||
'title': title,
|
||||
'body': body,
|
||||
'meta': metadata,
|
||||
'library': tag_library,
|
||||
})
|
||||
filters.append({
|
||||
'name': filter_name,
|
||||
'title': title,
|
||||
'body': body,
|
||||
'meta': metadata,
|
||||
'library': tag_library,
|
||||
})
|
||||
kwargs.update({'filters': filters})
|
||||
return super(TemplateFilterIndexView, self).get_context_data(**kwargs)
|
||||
|
||||
|
@ -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))
|
||||
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)
|
||||
# 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:
|
||||
for taglib in bits[1:]:
|
||||
# add the library to the parser
|
||||
try:
|
||||
lib = get_library(taglib)
|
||||
parser.add_library(lib)
|
||||
except InvalidTemplateLibrary as e:
|
||||
raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
|
||||
(taglib, e))
|
||||
# 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)
|
||||
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)
|
||||
|
|
|
@ -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,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
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue