* 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.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.http import Http404
|
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.template.engine import Engine
|
||||||
from django.utils._os import upath
|
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
@ -60,31 +55,32 @@ class TemplateTagIndexView(BaseAdminDocsView):
|
||||||
template_name = 'admin_doc/template_tag_index.html'
|
template_name = 'admin_doc/template_tag_index.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
load_all_installed_template_libraries()
|
|
||||||
|
|
||||||
tags = []
|
tags = []
|
||||||
app_libs = list(libraries.items())
|
try:
|
||||||
builtin_libs = [(None, lib) for lib in builtins]
|
engine = Engine.get_default()
|
||||||
for module_name, library in builtin_libs + app_libs:
|
except ImproperlyConfigured:
|
||||||
for tag_name, tag_func in library.tags.items():
|
# Non-trivial TEMPLATES settings aren't supported (#24125).
|
||||||
title, body, metadata = utils.parse_docstring(tag_func.__doc__)
|
pass
|
||||||
if title:
|
else:
|
||||||
title = utils.parse_rst(title, 'tag', _('tag:') + tag_name)
|
app_libs = sorted(engine.template_libraries.items())
|
||||||
if body:
|
builtin_libs = [('', lib) for lib in engine.template_builtins]
|
||||||
body = utils.parse_rst(body, 'tag', _('tag:') + tag_name)
|
for module_name, library in builtin_libs + app_libs:
|
||||||
for key in metadata:
|
for tag_name, tag_func in library.tags.items():
|
||||||
metadata[key] = utils.parse_rst(metadata[key], 'tag', _('tag:') + tag_name)
|
title, body, metadata = utils.parse_docstring(tag_func.__doc__)
|
||||||
if library in builtins:
|
if title:
|
||||||
tag_library = ''
|
title = utils.parse_rst(title, 'tag', _('tag:') + tag_name)
|
||||||
else:
|
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]
|
tag_library = module_name.split('.')[-1]
|
||||||
tags.append({
|
tags.append({
|
||||||
'name': tag_name,
|
'name': tag_name,
|
||||||
'title': title,
|
'title': title,
|
||||||
'body': body,
|
'body': body,
|
||||||
'meta': metadata,
|
'meta': metadata,
|
||||||
'library': tag_library,
|
'library': tag_library,
|
||||||
})
|
})
|
||||||
kwargs.update({'tags': tags})
|
kwargs.update({'tags': tags})
|
||||||
return super(TemplateTagIndexView, self).get_context_data(**kwargs)
|
return super(TemplateTagIndexView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
@ -93,31 +89,32 @@ class TemplateFilterIndexView(BaseAdminDocsView):
|
||||||
template_name = 'admin_doc/template_filter_index.html'
|
template_name = 'admin_doc/template_filter_index.html'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
load_all_installed_template_libraries()
|
|
||||||
|
|
||||||
filters = []
|
filters = []
|
||||||
app_libs = list(libraries.items())
|
try:
|
||||||
builtin_libs = [(None, lib) for lib in builtins]
|
engine = Engine.get_default()
|
||||||
for module_name, library in builtin_libs + app_libs:
|
except ImproperlyConfigured:
|
||||||
for filter_name, filter_func in library.filters.items():
|
# Non-trivial TEMPLATES settings aren't supported (#24125).
|
||||||
title, body, metadata = utils.parse_docstring(filter_func.__doc__)
|
pass
|
||||||
if title:
|
else:
|
||||||
title = utils.parse_rst(title, 'filter', _('filter:') + filter_name)
|
app_libs = sorted(engine.template_libraries.items())
|
||||||
if body:
|
builtin_libs = [('', lib) for lib in engine.template_builtins]
|
||||||
body = utils.parse_rst(body, 'filter', _('filter:') + filter_name)
|
for module_name, library in builtin_libs + app_libs:
|
||||||
for key in metadata:
|
for filter_name, filter_func in library.filters.items():
|
||||||
metadata[key] = utils.parse_rst(metadata[key], 'filter', _('filter:') + filter_name)
|
title, body, metadata = utils.parse_docstring(filter_func.__doc__)
|
||||||
if library in builtins:
|
if title:
|
||||||
tag_library = ''
|
title = utils.parse_rst(title, 'filter', _('filter:') + filter_name)
|
||||||
else:
|
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]
|
tag_library = module_name.split('.')[-1]
|
||||||
filters.append({
|
filters.append({
|
||||||
'name': filter_name,
|
'name': filter_name,
|
||||||
'title': title,
|
'title': title,
|
||||||
'body': body,
|
'body': body,
|
||||||
'meta': metadata,
|
'meta': metadata,
|
||||||
'library': tag_library,
|
'library': tag_library,
|
||||||
})
|
})
|
||||||
kwargs.update({'filters': filters})
|
kwargs.update({'filters': filters})
|
||||||
return super(TemplateFilterIndexView, self).get_context_data(**kwargs)
|
return super(TemplateFilterIndexView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
@ -320,29 +317,6 @@ class TemplateDetailView(BaseAdminDocsView):
|
||||||
# Helper functions #
|
# 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):
|
def get_return_data_type(func_name):
|
||||||
"""Return a somewhat-helpful data type given a function 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
|
from .base import resolve_variable # NOQA
|
||||||
|
|
||||||
# Library management
|
# Library management
|
||||||
from .base import Library # NOQA
|
from .library import Library # NOQA
|
||||||
|
|
||||||
|
|
||||||
__all__ += ('Template', 'Context', 'RequestContext')
|
__all__ += ('Template', 'Context', 'RequestContext')
|
||||||
|
|
|
@ -3,11 +3,15 @@ from __future__ import absolute_import
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
from importlib import import_module
|
||||||
|
from pkgutil import walk_packages
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template import TemplateDoesNotExist
|
from django.template import TemplateDoesNotExist
|
||||||
from django.template.context import Context, RequestContext, make_context
|
from django.template.context import Context, RequestContext, make_context
|
||||||
from django.template.engine import Engine, _dirs_undefined
|
from django.template.engine import Engine, _dirs_undefined
|
||||||
|
from django.template.library import InvalidTemplateLibrary
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.deprecation import RemovedInDjango20Warning
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
|
|
||||||
|
@ -23,6 +27,8 @@ class DjangoTemplates(BaseEngine):
|
||||||
options = params.pop('OPTIONS').copy()
|
options = params.pop('OPTIONS').copy()
|
||||||
options.setdefault('debug', settings.DEBUG)
|
options.setdefault('debug', settings.DEBUG)
|
||||||
options.setdefault('file_charset', settings.FILE_CHARSET)
|
options.setdefault('file_charset', settings.FILE_CHARSET)
|
||||||
|
libraries = options.get('libraries', {})
|
||||||
|
options['libraries'] = self.get_templatetag_libraries(libraries)
|
||||||
super(DjangoTemplates, self).__init__(params)
|
super(DjangoTemplates, self).__init__(params)
|
||||||
self.engine = Engine(self.dirs, self.app_dirs, **options)
|
self.engine = Engine(self.dirs, self.app_dirs, **options)
|
||||||
|
|
||||||
|
@ -35,6 +41,15 @@ class DjangoTemplates(BaseEngine):
|
||||||
except TemplateDoesNotExist as exc:
|
except TemplateDoesNotExist as exc:
|
||||||
reraise(exc, self)
|
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):
|
class Template(object):
|
||||||
|
|
||||||
|
@ -90,3 +105,48 @@ def reraise(exc, backend):
|
||||||
if hasattr(exc, 'template_debug'):
|
if hasattr(exc, 'template_debug'):
|
||||||
new.template_debug = exc.template_debug
|
new.template_debug = exc.template_debug
|
||||||
six.reraise(exc.__class__, new, sys.exc_info()[2])
|
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 logging
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from functools import partial
|
|
||||||
from importlib import import_module
|
|
||||||
from inspect import getargspec, getcallargs
|
from inspect import getargspec, getcallargs
|
||||||
|
|
||||||
from django.apps import apps
|
|
||||||
from django.template.context import ( # NOQA: imported for backwards compatibility
|
from django.template.context import ( # NOQA: imported for backwards compatibility
|
||||||
BaseContext, Context, ContextPopException, RequestContext,
|
BaseContext, Context, ContextPopException, RequestContext,
|
||||||
)
|
)
|
||||||
from django.utils import lru_cache, six
|
from django.utils import six
|
||||||
from django.utils.deprecation import (
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
RemovedInDjango20Warning, RemovedInDjango21Warning,
|
|
||||||
)
|
|
||||||
from django.utils.encoding import (
|
from django.utils.encoding import (
|
||||||
force_str, force_text, python_2_unicode_compatible,
|
force_str, force_text, python_2_unicode_compatible,
|
||||||
)
|
)
|
||||||
from django.utils.formats import localize
|
from django.utils.formats import localize
|
||||||
from django.utils.html import conditional_escape, escape
|
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 (
|
from django.utils.safestring import (
|
||||||
EscapeData, SafeData, mark_for_escaping, mark_safe,
|
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(VARIABLE_TAG_START), re.escape(VARIABLE_TAG_END),
|
||||||
re.escape(COMMENT_TAG_START), re.escape(COMMENT_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')
|
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)
|
return self.msg % tuple(force_text(p, errors='replace') for p in self.params)
|
||||||
|
|
||||||
|
|
||||||
class InvalidTemplateLibrary(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Origin(object):
|
class Origin(object):
|
||||||
def __init__(self, name, template_name=None, loader=None):
|
def __init__(self, name, template_name=None, loader=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -232,7 +216,9 @@ class Template(object):
|
||||||
lexer = Lexer(self.source)
|
lexer = Lexer(self.source)
|
||||||
|
|
||||||
tokens = lexer.tokenize()
|
tokens = lexer.tokenize()
|
||||||
parser = Parser(tokens)
|
parser = Parser(
|
||||||
|
tokens, self.engine.template_libraries, self.engine.template_builtins,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return parser.parse()
|
return parser.parse()
|
||||||
|
@ -452,13 +438,20 @@ class DebugLexer(Lexer):
|
||||||
|
|
||||||
|
|
||||||
class Parser(object):
|
class Parser(object):
|
||||||
def __init__(self, tokens):
|
def __init__(self, tokens, libraries=None, builtins=None):
|
||||||
self.tokens = tokens
|
self.tokens = tokens
|
||||||
self.tags = {}
|
self.tags = {}
|
||||||
self.filters = {}
|
self.filters = {}
|
||||||
self.command_stack = []
|
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):
|
def parse(self, parse_until=None):
|
||||||
"""
|
"""
|
||||||
|
@ -1073,377 +1066,3 @@ def token_kwargs(bits, parser, support_legacy=False):
|
||||||
return kwargs
|
return kwargs
|
||||||
del bits[:1]
|
del bits[:1]
|
||||||
return kwargs
|
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.timesince import timesince, timeuntil
|
||||||
from django.utils.translation import ugettext, ungettext
|
from django.utils.translation import ugettext, ungettext
|
||||||
|
|
||||||
from .base import Library, Variable, VariableDoesNotExist
|
from .base import Variable, VariableDoesNotExist
|
||||||
|
from .library import Library
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,12 @@ from django.utils.safestring import mark_safe
|
||||||
from .base import (
|
from .base import (
|
||||||
BLOCK_TAG_END, BLOCK_TAG_START, COMMENT_TAG_END, COMMENT_TAG_START,
|
BLOCK_TAG_END, BLOCK_TAG_START, COMMENT_TAG_END, COMMENT_TAG_START,
|
||||||
SINGLE_BRACE_END, SINGLE_BRACE_START, VARIABLE_ATTRIBUTE_SEPARATOR,
|
SINGLE_BRACE_END, SINGLE_BRACE_START, VARIABLE_ATTRIBUTE_SEPARATOR,
|
||||||
VARIABLE_TAG_END, VARIABLE_TAG_START, Context, InvalidTemplateLibrary,
|
VARIABLE_TAG_END, VARIABLE_TAG_START, Context, Node, NodeList, Template,
|
||||||
Library, Node, NodeList, Template, TemplateSyntaxError,
|
TemplateSyntaxError, VariableDoesNotExist, kwarg_re,
|
||||||
VariableDoesNotExist, get_library, kwarg_re, render_value_in_context,
|
render_value_in_context, token_kwargs,
|
||||||
token_kwargs,
|
|
||||||
)
|
)
|
||||||
from .defaultfilters import date
|
from .defaultfilters import date
|
||||||
|
from .library import Library
|
||||||
from .smartif import IfParser, Literal
|
from .smartif import IfParser, Literal
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
@ -1121,10 +1121,43 @@ def ssi(parser, token):
|
||||||
return SsiNode(filepath, parsed)
|
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
|
@register.tag
|
||||||
def load(parser, token):
|
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
|
For example, to load the template tags in
|
||||||
``django/templatetags/news/photos.py``::
|
``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
|
# token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
|
||||||
bits = token.contents.split()
|
bits = token.contents.split()
|
||||||
if len(bits) >= 4 and bits[-2] == "from":
|
if len(bits) >= 4 and bits[-2] == "from":
|
||||||
try:
|
# from syntax is used; load individual tags from the library
|
||||||
taglib = bits[-1]
|
name = bits[-1]
|
||||||
lib = get_library(taglib)
|
lib = find_library(parser, name)
|
||||||
except InvalidTemplateLibrary as e:
|
subset = load_from_library(lib, name, bits[1:-2])
|
||||||
raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
|
parser.add_library(subset)
|
||||||
(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)
|
|
||||||
else:
|
else:
|
||||||
for taglib in bits[1:]:
|
# one or more libraries are specified; load and add them to the parser
|
||||||
# add the library to the parser
|
for name in bits[1:]:
|
||||||
try:
|
lib = find_library(parser, name)
|
||||||
lib = get_library(taglib)
|
parser.add_library(lib)
|
||||||
parser.add_library(lib)
|
|
||||||
except InvalidTemplateLibrary as e:
|
|
||||||
raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
|
|
||||||
(taglib, e))
|
|
||||||
return LoadNode()
|
return LoadNode()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ from django.utils.module_loading import import_string
|
||||||
from .base import Context, Template
|
from .base import Context, Template
|
||||||
from .context import _builtin_context_processors
|
from .context import _builtin_context_processors
|
||||||
from .exceptions import TemplateDoesNotExist
|
from .exceptions import TemplateDoesNotExist
|
||||||
|
from .library import import_library
|
||||||
|
|
||||||
_context_instance_undefined = object()
|
_context_instance_undefined = object()
|
||||||
_dictionary_undefined = object()
|
_dictionary_undefined = object()
|
||||||
|
@ -16,11 +17,16 @@ _dirs_undefined = object()
|
||||||
|
|
||||||
|
|
||||||
class Engine(object):
|
class Engine(object):
|
||||||
|
default_builtins = [
|
||||||
|
'django.template.defaulttags',
|
||||||
|
'django.template.defaultfilters',
|
||||||
|
'django.template.loader_tags',
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(self, dirs=None, app_dirs=False,
|
def __init__(self, dirs=None, app_dirs=False,
|
||||||
allowed_include_roots=None, context_processors=None,
|
allowed_include_roots=None, context_processors=None,
|
||||||
debug=False, loaders=None, string_if_invalid='',
|
debug=False, loaders=None, string_if_invalid='',
|
||||||
file_charset='utf-8'):
|
file_charset='utf-8', libraries=None, builtins=None):
|
||||||
if dirs is None:
|
if dirs is None:
|
||||||
dirs = []
|
dirs = []
|
||||||
if allowed_include_roots is None:
|
if allowed_include_roots is None:
|
||||||
|
@ -35,6 +41,10 @@ class Engine(object):
|
||||||
if app_dirs:
|
if app_dirs:
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
"app_dirs must not be set when loaders is defined.")
|
"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):
|
if isinstance(allowed_include_roots, six.string_types):
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
|
@ -48,6 +58,10 @@ class Engine(object):
|
||||||
self.loaders = loaders
|
self.loaders = loaders
|
||||||
self.string_if_invalid = string_if_invalid
|
self.string_if_invalid = string_if_invalid
|
||||||
self.file_charset = file_charset
|
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
|
@staticmethod
|
||||||
@lru_cache.lru_cache()
|
@lru_cache.lru_cache()
|
||||||
|
@ -90,6 +104,15 @@ class Engine(object):
|
||||||
context_processors += tuple(self.context_processors)
|
context_processors += tuple(self.context_processors)
|
||||||
return tuple(import_string(path) for path in 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
|
@cached_property
|
||||||
def template_loaders(self):
|
def template_loaders(self):
|
||||||
return self.get_template_loaders(self.loaders)
|
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 django.utils.safestring import mark_safe
|
||||||
|
|
||||||
from .base import (
|
from .base import (
|
||||||
Library, Node, Template, TemplateSyntaxError, TextNode, Variable,
|
Node, Template, TemplateSyntaxError, TextNode, Variable, token_kwargs,
|
||||||
token_kwargs,
|
|
||||||
)
|
)
|
||||||
|
from .library import Library
|
||||||
|
|
||||||
register = Library()
|
register = Library()
|
||||||
|
|
||||||
|
|
|
@ -35,9 +35,6 @@ def update_installed_apps(**kwargs):
|
||||||
# Rebuild management commands cache
|
# Rebuild management commands cache
|
||||||
from django.core.management import get_commands
|
from django.core.management import get_commands
|
||||||
get_commands.cache_clear()
|
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.
|
# Rebuild get_app_template_dirs cache.
|
||||||
from django.template.utils import get_app_template_dirs
|
from django.template.utils import get_app_template_dirs
|
||||||
get_app_template_dirs.cache_clear()
|
get_app_template_dirs.cache_clear()
|
||||||
|
|
|
@ -13,9 +13,11 @@ available to your templates using the :ttag:`{% load %}<load>` tag.
|
||||||
Code layout
|
Code layout
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
Custom template tags and filters must live inside a Django app. If they relate
|
The most common place to specify custom template tags and filters is inside
|
||||||
to an existing app it makes sense to bundle them there; otherwise, you should
|
a Django app. If they relate to an existing app, it makes sense to bundle them
|
||||||
create a new app to hold 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
|
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 -
|
``models.py``, ``views.py``, etc. If this doesn't already exist, create it -
|
||||||
|
@ -63,6 +65,15 @@ following::
|
||||||
|
|
||||||
register = template.Library()
|
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
|
.. admonition:: Behind the scenes
|
||||||
|
|
||||||
For a ton of examples, read the source code for Django's default filters
|
For a ton of examples, read the source code for Django's default filters
|
||||||
|
|
|
@ -41,7 +41,7 @@ lower level APIs:
|
||||||
Configuring an engine
|
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
|
.. versionadded:: 1.8
|
||||||
|
|
||||||
|
@ -114,6 +114,34 @@ Configuring an engine
|
||||||
|
|
||||||
It defaults to ``'utf-8'``.
|
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()
|
.. staticmethod:: Engine.get_default()
|
||||||
|
|
||||||
When a Django project configures one and only one
|
When a Django project configures one and only one
|
||||||
|
|
|
@ -263,6 +263,10 @@ Templates
|
||||||
* :ref:`Debug page integration <template-debug-integration>` for custom
|
* :ref:`Debug page integration <template-debug-integration>` for custom
|
||||||
template engines was added.
|
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
|
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>`
|
even if you're using :meth:`raw() <django.db.models.query.QuerySet.raw>`
|
||||||
queries. The ORM takes care of managing time zone information.
|
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
|
Miscellaneous
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -401,6 +401,34 @@ applications. This generic name was kept for backwards-compatibility.
|
||||||
|
|
||||||
It defaults to the value of :setting:`FILE_CHARSET`.
|
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
|
.. module:: django.template.backends.jinja2
|
||||||
|
|
||||||
.. class:: 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 import RequestContext
|
||||||
from django.template.backends.django import DjangoTemplates
|
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 django.utils.deprecation import RemovedInDjango20Warning
|
||||||
|
|
||||||
from .test_dummy import TemplateStringsTests
|
from .test_dummy import TemplateStringsTests
|
||||||
|
@ -51,3 +52,78 @@ class DjangoTemplatesTests(TemplateStringsTests):
|
||||||
"the two arguments refer to the same request.")
|
"the two arguments refer to the same request.")
|
||||||
with self.assertRaisesMessage(ValueError, msg):
|
with self.assertRaisesMessage(ValueError, msg):
|
||||||
template.render(request_context, other_request)
|
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):
|
class CacheTagTests(SimpleTestCase):
|
||||||
|
libraries = {
|
||||||
|
'cache': 'django.templatetags.cache',
|
||||||
|
'custom': 'template_tests.templatetags.custom',
|
||||||
|
}
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
cache.clear()
|
cache.clear()
|
||||||
|
@ -121,7 +125,7 @@ class CacheTests(SimpleTestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.engine = Engine()
|
cls.engine = Engine(libraries={'cache': 'django.templatetags.cache'})
|
||||||
super(CacheTests, cls).setUpClass()
|
super(CacheTests, cls).setUpClass()
|
||||||
|
|
||||||
def test_cache_regression_20130(self):
|
def test_cache_regression_20130(self):
|
||||||
|
|
|
@ -6,6 +6,7 @@ from ..utils import setup
|
||||||
|
|
||||||
|
|
||||||
class CycleTagTests(SimpleTestCase):
|
class CycleTagTests(SimpleTestCase):
|
||||||
|
libraries = {'future': 'django.templatetags.future'}
|
||||||
|
|
||||||
@setup({'cycle01': '{% cycle a %}'})
|
@setup({'cycle01': '{% cycle a %}'})
|
||||||
def test_cycle01(self):
|
def test_cycle01(self):
|
||||||
|
|
|
@ -56,6 +56,7 @@ inheritance_templates = {
|
||||||
|
|
||||||
|
|
||||||
class InheritanceTests(SimpleTestCase):
|
class InheritanceTests(SimpleTestCase):
|
||||||
|
libraries = {'testtags': 'template_tests.templatetags.testtags'}
|
||||||
|
|
||||||
@setup(inheritance_templates)
|
@setup(inheritance_templates)
|
||||||
def test_inheritance01(self):
|
def test_inheritance01(self):
|
||||||
|
|
|
@ -6,6 +6,7 @@ from ..utils import setup
|
||||||
|
|
||||||
|
|
||||||
class FirstOfTagTests(SimpleTestCase):
|
class FirstOfTagTests(SimpleTestCase):
|
||||||
|
libraries = {'future': 'django.templatetags.future'}
|
||||||
|
|
||||||
@setup({'firstof01': '{% firstof a b c %}'})
|
@setup({'firstof01': '{% firstof a b c %}'})
|
||||||
def test_firstof01(self):
|
def test_firstof01(self):
|
||||||
|
|
|
@ -6,6 +6,7 @@ from ..utils import setup
|
||||||
|
|
||||||
|
|
||||||
class ForTagTests(SimpleTestCase):
|
class ForTagTests(SimpleTestCase):
|
||||||
|
libraries = {'custom': 'template_tests.templatetags.custom'}
|
||||||
|
|
||||||
@setup({'for-tag01': '{% for val in values %}{{ val }}{% endfor %}'})
|
@setup({'for-tag01': '{% for val in values %}{{ val }}{% endfor %}'})
|
||||||
def test_for_tag01(self):
|
def test_for_tag01(self):
|
||||||
|
|
|
@ -10,6 +10,10 @@ from ..utils import setup
|
||||||
|
|
||||||
|
|
||||||
class I18nTagTests(SimpleTestCase):
|
class I18nTagTests(SimpleTestCase):
|
||||||
|
libraries = {
|
||||||
|
'custom': 'template_tests.templatetags.custom',
|
||||||
|
'i18n': 'django.templatetags.i18n',
|
||||||
|
}
|
||||||
|
|
||||||
@setup({'i18n01': '{% load i18n %}{% trans \'xxxyyyxxx\' %}'})
|
@setup({'i18n01': '{% load i18n %}{% trans \'xxxyyyxxx\' %}'})
|
||||||
def test_i18n01(self):
|
def test_i18n01(self):
|
||||||
|
|
|
@ -5,6 +5,7 @@ from ..utils import setup
|
||||||
|
|
||||||
|
|
||||||
class IfChangedTagTests(SimpleTestCase):
|
class IfChangedTagTests(SimpleTestCase):
|
||||||
|
libraries = {'custom': 'template_tests.templatetags.custom'}
|
||||||
|
|
||||||
@setup({'ifchanged01': '{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}'})
|
@setup({'ifchanged01': '{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}'})
|
||||||
def test_ifchanged01(self):
|
def test_ifchanged01(self):
|
||||||
|
|
|
@ -13,6 +13,7 @@ include_fail_templates = {
|
||||||
|
|
||||||
|
|
||||||
class IncludeTagTests(SimpleTestCase):
|
class IncludeTagTests(SimpleTestCase):
|
||||||
|
libraries = {'bad_tag': 'template_tests.templatetags.bad_tag'}
|
||||||
|
|
||||||
@setup({'include01': '{% include "basic-syntax01" %}'}, basic_templates)
|
@setup({'include01': '{% include "basic-syntax01" %}'}, basic_templates)
|
||||||
def test_include01(self):
|
def test_include01(self):
|
||||||
|
|
|
@ -4,6 +4,7 @@ from ..utils import setup
|
||||||
|
|
||||||
|
|
||||||
class InvalidStringTests(SimpleTestCase):
|
class InvalidStringTests(SimpleTestCase):
|
||||||
|
libraries = {'i18n': 'django.templatetags.i18n'}
|
||||||
|
|
||||||
@setup({'invalidstr01': '{{ var|default:"Foo" }}'})
|
@setup({'invalidstr01': '{{ var|default:"Foo" }}'})
|
||||||
def test_invalidstr01(self):
|
def test_invalidstr01(self):
|
||||||
|
|
|
@ -5,6 +5,10 @@ from ..utils import setup
|
||||||
|
|
||||||
|
|
||||||
class LoadTagTests(SimpleTestCase):
|
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" %}'})
|
@setup({'load01': '{% load testtags subpackage.echo %}{% echo test %} {% echo2 "test" %}'})
|
||||||
def test_load01(self):
|
def test_load01(self):
|
||||||
|
@ -42,30 +46,30 @@ class LoadTagTests(SimpleTestCase):
|
||||||
# {% load %} tag errors
|
# {% load %} tag errors
|
||||||
@setup({'load07': '{% load echo other_echo bad_tag from testtags %}'})
|
@setup({'load07': '{% load echo other_echo bad_tag from testtags %}'})
|
||||||
def test_load07(self):
|
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')
|
self.engine.get_template('load07')
|
||||||
|
|
||||||
@setup({'load08': '{% load echo other_echo bad_tag from %}'})
|
@setup({'load08': '{% load echo other_echo bad_tag from %}'})
|
||||||
def test_load08(self):
|
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')
|
self.engine.get_template('load08')
|
||||||
|
|
||||||
@setup({'load09': '{% load from testtags %}'})
|
@setup({'load09': '{% load from testtags %}'})
|
||||||
def test_load09(self):
|
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')
|
self.engine.get_template('load09')
|
||||||
|
|
||||||
@setup({'load10': '{% load echo from bad_library %}'})
|
@setup({'load10': '{% load echo from bad_library %}'})
|
||||||
def test_load10(self):
|
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')
|
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 %}'})
|
@setup({'load12': '{% load subpackage.missing %}'})
|
||||||
def test_load12(self):
|
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')
|
self.engine.get_template('load12')
|
||||||
|
|
|
@ -5,6 +5,7 @@ from ..utils import setup
|
||||||
|
|
||||||
|
|
||||||
class SimpleTagTests(SimpleTestCase):
|
class SimpleTagTests(SimpleTestCase):
|
||||||
|
libraries = {'custom': 'template_tests.templatetags.custom'}
|
||||||
|
|
||||||
@setup({'simpletag-renamed01': '{% load custom %}{% minusone 7 %}'})
|
@setup({'simpletag-renamed01': '{% load custom %}{% minusone 7 %}'})
|
||||||
def test_simpletag_renamed01(self):
|
def test_simpletag_renamed01(self):
|
||||||
|
|
|
@ -7,6 +7,7 @@ from ..utils import setup
|
||||||
|
|
||||||
@override_settings(MEDIA_URL="/media/", STATIC_URL="/static/")
|
@override_settings(MEDIA_URL="/media/", STATIC_URL="/static/")
|
||||||
class StaticTagTests(SimpleTestCase):
|
class StaticTagTests(SimpleTestCase):
|
||||||
|
libraries = {'static': 'django.templatetags.static'}
|
||||||
|
|
||||||
@setup({'static-prefixtag01': '{% load static %}{% get_static_prefix %}'})
|
@setup({'static-prefixtag01': '{% load static %}{% get_static_prefix %}'})
|
||||||
def test_static_prefixtag01(self):
|
def test_static_prefixtag01(self):
|
||||||
|
|
|
@ -6,6 +6,7 @@ from ..utils import setup
|
||||||
|
|
||||||
|
|
||||||
class WidthRatioTagTests(SimpleTestCase):
|
class WidthRatioTagTests(SimpleTestCase):
|
||||||
|
libraries = {'custom': 'template_tests.templatetags.custom'}
|
||||||
|
|
||||||
@setup({'widthratio01': '{% widthratio a b 0 %}'})
|
@setup({'widthratio01': '{% widthratio a b 0 %}'})
|
||||||
def test_widthratio01(self):
|
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 import Context, Engine, TemplateSyntaxError
|
||||||
from django.template.base import Node
|
from django.template.base import Node
|
||||||
|
from django.template.library import InvalidTemplateLibrary
|
||||||
from django.test import SimpleTestCase, ignore_warnings
|
from django.test import SimpleTestCase, ignore_warnings
|
||||||
from django.test.utils import extend_sys_path
|
from django.test.utils import extend_sys_path
|
||||||
|
from django.utils import six
|
||||||
from django.utils.deprecation import RemovedInDjango20Warning
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
|
|
||||||
from .templatetags import custom, inclusion
|
from .templatetags import custom, inclusion
|
||||||
from .utils import ROOT
|
from .utils import ROOT
|
||||||
|
|
||||||
|
LIBRARIES = {
|
||||||
|
'custom': 'template_tests.templatetags.custom',
|
||||||
|
'inclusion': 'template_tests.templatetags.inclusion',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class CustomFilterTests(SimpleTestCase):
|
class CustomFilterTests(SimpleTestCase):
|
||||||
|
|
||||||
def test_filter(self):
|
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(
|
self.assertEqual(
|
||||||
t.render(Context({"string": "abcdefghijklmnopqrstuvwxyz"})),
|
t.render(Context({"string": "abcdefghijklmnopqrstuvwxyz"})),
|
||||||
"abcde"
|
"abcde"
|
||||||
|
@ -26,7 +34,7 @@ class TagTestCase(SimpleTestCase):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.engine = Engine(app_dirs=True)
|
cls.engine = Engine(app_dirs=True, libraries=LIBRARIES)
|
||||||
super(TagTestCase, cls).setUpClass()
|
super(TagTestCase, cls).setUpClass()
|
||||||
|
|
||||||
def verify_tag(self, tag, name):
|
def verify_tag(self, tag, name):
|
||||||
|
@ -269,7 +277,7 @@ class InclusionTagTests(TagTestCase):
|
||||||
"""
|
"""
|
||||||
#23441 -- InclusionNode shouldn't modify its nodelist at render time.
|
#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 %}')
|
template = engine.from_string('{% load inclusion %}{% inclusion_no_params %}')
|
||||||
count = template.nodelist.get_nodes_by_type(Node)
|
count = template.nodelist.get_nodes_by_type(Node)
|
||||||
template.render(Context({}))
|
template.render(Context({}))
|
||||||
|
@ -281,7 +289,7 @@ class InclusionTagTests(TagTestCase):
|
||||||
when rendering. Otherwise, leftover values such as blocks from
|
when rendering. Otherwise, leftover values such as blocks from
|
||||||
extending can interfere with subsequent rendering.
|
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 %}')
|
template = engine.from_string('{% load inclusion %}{% inclusion_extends1 %}{% inclusion_extends2 %}')
|
||||||
self.assertEqual(template.render(Context({})).strip(), 'one\ntwo')
|
self.assertEqual(template.render(Context({})).strip(), 'one\ntwo')
|
||||||
|
|
||||||
|
@ -313,34 +321,37 @@ class TemplateTagLoadingTests(SimpleTestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.egg_dir = os.path.join(ROOT, 'eggs')
|
cls.egg_dir = os.path.join(ROOT, 'eggs')
|
||||||
cls.engine = Engine()
|
|
||||||
super(TemplateTagLoadingTests, cls).setUpClass()
|
super(TemplateTagLoadingTests, cls).setUpClass()
|
||||||
|
|
||||||
def test_load_error(self):
|
def test_load_error(self):
|
||||||
ttext = "{% load broken_tag %}"
|
msg = (
|
||||||
with self.assertRaises(TemplateSyntaxError) as e:
|
"Invalid template library specified. ImportError raised when "
|
||||||
self.engine.from_string(ttext)
|
"trying to load 'template_tests.broken_tag': cannot import name "
|
||||||
|
"'?Xtemplate'?"
|
||||||
self.assertIn('ImportError', e.exception.args[0])
|
)
|
||||||
self.assertIn('Xtemplate', e.exception.args[0])
|
with six.assertRaisesRegex(self, InvalidTemplateLibrary, msg):
|
||||||
|
Engine(libraries={
|
||||||
|
'broken_tag': 'template_tests.broken_tag',
|
||||||
|
})
|
||||||
|
|
||||||
def test_load_error_egg(self):
|
def test_load_error_egg(self):
|
||||||
ttext = "{% load broken_egg %}"
|
|
||||||
egg_name = '%s/tagsegg.egg' % self.egg_dir
|
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 extend_sys_path(egg_name):
|
||||||
with self.assertRaises(TemplateSyntaxError):
|
with six.assertRaisesRegex(self, InvalidTemplateLibrary, msg):
|
||||||
with self.settings(INSTALLED_APPS=['tagsegg']):
|
Engine(libraries={
|
||||||
self.engine.from_string(ttext)
|
'broken_egg': 'tagsegg.templatetags.broken_egg',
|
||||||
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])
|
|
||||||
|
|
||||||
def test_load_working_egg(self):
|
def test_load_working_egg(self):
|
||||||
ttext = "{% load working_egg %}"
|
ttext = "{% load working_egg %}"
|
||||||
egg_name = '%s/tagsegg.egg' % self.egg_dir
|
egg_name = '%s/tagsegg.egg' % self.egg_dir
|
||||||
with extend_sys_path(egg_name):
|
with extend_sys_path(egg_name):
|
||||||
with self.settings(INSTALLED_APPS=['tagsegg']):
|
engine = Engine(libraries={
|
||||||
self.engine.from_string(ttext)
|
'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):
|
class DeprecatedRenderToStringTest(SimpleTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
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):
|
def test_basic_context(self):
|
||||||
self.assertEqual(
|
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),
|
'range': range(5),
|
||||||
'five': 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:
|
for source, expected_error_source_index in tests:
|
||||||
template = engine.from_string(source)
|
template = engine.from_string(source)
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -9,6 +9,7 @@ from django.template import Library, TemplateSyntaxError
|
||||||
from django.template.base import (
|
from django.template.base import (
|
||||||
TOKEN_BLOCK, FilterExpression, Parser, Token, Variable,
|
TOKEN_BLOCK, FilterExpression, Parser, Token, Variable,
|
||||||
)
|
)
|
||||||
|
from django.template.defaultfilters import register as filter_library
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ class ParserTests(TestCase):
|
||||||
|
|
||||||
def test_filter_parsing(self):
|
def test_filter_parsing(self):
|
||||||
c = {"article": {"section": "News"}}
|
c = {"article": {"section": "News"}}
|
||||||
p = Parser("")
|
p = Parser("", builtins=[filter_library])
|
||||||
|
|
||||||
def fe_test(s, val):
|
def fe_test(s, val):
|
||||||
self.assertEqual(FilterExpression(s, p).resolve(c), 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
|
Errors raised while compiling nodes should include the token
|
||||||
information.
|
information.
|
||||||
"""
|
"""
|
||||||
engine = Engine(debug=True)
|
engine = Engine(
|
||||||
|
debug=True,
|
||||||
|
libraries={'bad_tag': 'template_tests.templatetags.bad_tag'},
|
||||||
|
)
|
||||||
with self.assertRaises(RuntimeError) as e:
|
with self.assertRaises(RuntimeError) as e:
|
||||||
engine.from_string("{% load bad_tag %}{% badtag %}")
|
engine.from_string("{% load bad_tag %}{% badtag %}")
|
||||||
self.assertEqual(e.exception.template_debug['during'], '{% badtag %}')
|
self.assertEqual(e.exception.template_debug['during'], '{% badtag %}')
|
||||||
|
|
|
@ -5,9 +5,6 @@ from __future__ import unicode_literals
|
||||||
import functools
|
import functools
|
||||||
import os
|
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.template.engine import Engine
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
from django.utils._os import upath
|
from django.utils._os import upath
|
||||||
|
@ -49,14 +46,17 @@ def setup(templates, *args, **kwargs):
|
||||||
]
|
]
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
@register_test_tags
|
|
||||||
# Make Engine.get_default() raise an exception to ensure that tests
|
# Make Engine.get_default() raise an exception to ensure that tests
|
||||||
# are properly isolated from Django's global settings.
|
# are properly isolated from Django's global settings.
|
||||||
@override_settings(TEMPLATES=None)
|
@override_settings(TEMPLATES=None)
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def inner(self):
|
def inner(self):
|
||||||
|
# Set up custom template tag libraries if specified
|
||||||
|
libraries = getattr(self, 'libraries', {})
|
||||||
|
|
||||||
self.engine = Engine(
|
self.engine = Engine(
|
||||||
allowed_include_roots=[ROOT],
|
allowed_include_roots=[ROOT],
|
||||||
|
libraries=libraries,
|
||||||
loaders=loaders,
|
loaders=loaders,
|
||||||
)
|
)
|
||||||
func(self)
|
func(self)
|
||||||
|
@ -66,6 +66,7 @@ def setup(templates, *args, **kwargs):
|
||||||
|
|
||||||
self.engine = Engine(
|
self.engine = Engine(
|
||||||
allowed_include_roots=[ROOT],
|
allowed_include_roots=[ROOT],
|
||||||
|
libraries=libraries,
|
||||||
loaders=loaders,
|
loaders=loaders,
|
||||||
string_if_invalid='INVALID',
|
string_if_invalid='INVALID',
|
||||||
)
|
)
|
||||||
|
@ -75,6 +76,7 @@ def setup(templates, *args, **kwargs):
|
||||||
self.engine = Engine(
|
self.engine = Engine(
|
||||||
allowed_include_roots=[ROOT],
|
allowed_include_roots=[ROOT],
|
||||||
debug=True,
|
debug=True,
|
||||||
|
libraries=libraries,
|
||||||
loaders=loaders,
|
loaders=loaders,
|
||||||
)
|
)
|
||||||
func(self)
|
func(self)
|
||||||
|
@ -85,43 +87,9 @@ def setup(templates, *args, **kwargs):
|
||||||
return decorator
|
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
|
# Helper objects
|
||||||
|
|
||||||
|
|
||||||
class SomeException(Exception):
|
class SomeException(Exception):
|
||||||
silent_variable_failure = True
|
silent_variable_failure = True
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue