engine.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import warnings
  2. from django.core.exceptions import ImproperlyConfigured
  3. from django.utils import lru_cache
  4. from django.utils import six
  5. from django.utils.deprecation import RemovedInDjango20Warning
  6. from django.utils.functional import cached_property
  7. from django.utils.module_loading import import_string
  8. from . import engines
  9. from .base import Context, Lexer, Parser, Template, TemplateDoesNotExist
  10. from .context import _builtin_context_processors
  11. _context_instance_undefined = object()
  12. _dictionary_undefined = object()
  13. _dirs_undefined = object()
  14. class Engine(object):
  15. def __init__(self, dirs=None, app_dirs=False,
  16. allowed_include_roots=None, context_processors=None,
  17. debug=False, loaders=None, string_if_invalid='',
  18. file_charset='utf-8'):
  19. if dirs is None:
  20. dirs = []
  21. if allowed_include_roots is None:
  22. allowed_include_roots = []
  23. if context_processors is None:
  24. context_processors = []
  25. if loaders is None:
  26. loaders = ['django.template.loaders.filesystem.Loader']
  27. if app_dirs:
  28. loaders += ['django.template.loaders.app_directories.Loader']
  29. else:
  30. if app_dirs:
  31. raise ImproperlyConfigured(
  32. "app_dirs must not be set when loaders is defined.")
  33. if isinstance(allowed_include_roots, six.string_types):
  34. raise ImproperlyConfigured(
  35. "allowed_include_roots must be a tuple, not a string.")
  36. self.dirs = dirs
  37. self.app_dirs = app_dirs
  38. self.allowed_include_roots = allowed_include_roots
  39. self.context_processors = context_processors
  40. self.debug = debug
  41. self.loaders = loaders
  42. self.string_if_invalid = string_if_invalid
  43. self.file_charset = file_charset
  44. @staticmethod
  45. @lru_cache.lru_cache()
  46. def get_default():
  47. """
  48. When only one DjangoTemplates backend is configured, returns it.
  49. Raises ImproperlyConfigured otherwise.
  50. This is required for preserving historical APIs that rely on a
  51. globally available, implicitly configured engine such as:
  52. >>> from django.template import Context, Template
  53. >>> template = Template("Hello {{ name }}!")
  54. >>> context = Context({'name': "world"})
  55. >>> template.render(context)
  56. 'Hello world!'
  57. """
  58. # Since DjangoTemplates is a wrapper around this Engine class, a local
  59. # import is mandatory to avoid an import loop.
  60. from django.template.backends.django import DjangoTemplates
  61. django_engines = [engine for engine in engines.all()
  62. if isinstance(engine, DjangoTemplates)]
  63. if len(django_engines) == 1:
  64. # Unwrap the Engine instance inside DjangoTemplates
  65. return django_engines[0].engine
  66. elif len(django_engines) == 0:
  67. raise ImproperlyConfigured(
  68. "No DjangoTemplates backend is configured.")
  69. else:
  70. raise ImproperlyConfigured(
  71. "Several DjangoTemplates backends are configured. "
  72. "You must select one explicitly.")
  73. @cached_property
  74. def template_context_processors(self):
  75. context_processors = _builtin_context_processors
  76. context_processors += tuple(self.context_processors)
  77. return tuple(import_string(path) for path in context_processors)
  78. @cached_property
  79. def template_loaders(self):
  80. return self.get_template_loaders(self.loaders)
  81. def get_template_loaders(self, template_loaders):
  82. loaders = []
  83. for template_loader in template_loaders:
  84. loader = self.find_template_loader(template_loader)
  85. if loader is not None:
  86. loaders.append(loader)
  87. return loaders
  88. def find_template_loader(self, loader):
  89. if isinstance(loader, (tuple, list)):
  90. args = list(loader[1:])
  91. loader = loader[0]
  92. else:
  93. args = []
  94. if isinstance(loader, six.string_types):
  95. loader_class = import_string(loader)
  96. if getattr(loader_class, '_accepts_engine_in_init', False):
  97. args.insert(0, self)
  98. else:
  99. warnings.warn(
  100. "%s inherits from django.template.loader.BaseLoader "
  101. "instead of django.template.loaders.base.Loader. " %
  102. loader, RemovedInDjango20Warning, stacklevel=2)
  103. loader_instance = loader_class(*args)
  104. if not loader_instance.is_usable:
  105. warnings.warn(
  106. "Your template loaders configuration includes %r, but "
  107. "your Python installation doesn't support that type of "
  108. "template loading. Consider removing that line from "
  109. "your settings." % loader)
  110. return None
  111. else:
  112. return loader_instance
  113. else:
  114. raise ImproperlyConfigured(
  115. "Invalid value in template loaders configuration: %r" % loader)
  116. def find_template(self, name, dirs=None):
  117. for loader in self.template_loaders:
  118. try:
  119. source, display_name = loader(name, dirs)
  120. origin = self.make_origin(display_name, loader, name, dirs)
  121. return source, origin
  122. except TemplateDoesNotExist:
  123. pass
  124. raise TemplateDoesNotExist(name)
  125. def from_string(self, template_code):
  126. """
  127. Returns a compiled Template object for the given template code,
  128. handling template inheritance recursively.
  129. """
  130. return Template(template_code, engine=self)
  131. def get_template(self, template_name, dirs=_dirs_undefined):
  132. """
  133. Returns a compiled Template object for the given template name,
  134. handling template inheritance recursively.
  135. """
  136. if dirs is _dirs_undefined:
  137. dirs = None
  138. else:
  139. warnings.warn(
  140. "The dirs argument of get_template is deprecated.",
  141. RemovedInDjango20Warning, stacklevel=2)
  142. template, origin = self.find_template(template_name, dirs)
  143. if not hasattr(template, 'render'):
  144. # template needs to be compiled
  145. template = Template(template, origin, template_name, engine=self)
  146. return template
  147. def render_to_string(self, template_name, context=None,
  148. context_instance=_context_instance_undefined,
  149. dirs=_dirs_undefined,
  150. dictionary=_dictionary_undefined):
  151. """
  152. Loads the given template_name and renders it with the given dictionary as
  153. context. The template_name may be a string to load a single template using
  154. get_template, or it may be a tuple to use select_template to find one of
  155. the templates in the list. Returns a string.
  156. """
  157. if context_instance is _context_instance_undefined:
  158. context_instance = None
  159. else:
  160. warnings.warn(
  161. "The context_instance argument of render_to_string is "
  162. "deprecated.", RemovedInDjango20Warning, stacklevel=2)
  163. if dirs is _dirs_undefined:
  164. # Do not set dirs to None here to avoid triggering the deprecation
  165. # warning in select_template or get_template.
  166. pass
  167. else:
  168. warnings.warn(
  169. "The dirs argument of render_to_string is deprecated.",
  170. RemovedInDjango20Warning, stacklevel=2)
  171. if dictionary is _dictionary_undefined:
  172. dictionary = None
  173. else:
  174. warnings.warn(
  175. "The dictionary argument of render_to_string was renamed to "
  176. "context.", RemovedInDjango20Warning, stacklevel=2)
  177. context = dictionary
  178. if isinstance(template_name, (list, tuple)):
  179. t = self.select_template(template_name, dirs)
  180. else:
  181. t = self.get_template(template_name, dirs)
  182. if not context_instance:
  183. # Django < 1.8 accepted a Context in `context` even though that's
  184. # unintended. Preserve this ability but don't rewrap `context`.
  185. if isinstance(context, Context):
  186. return t.render(context)
  187. else:
  188. return t.render(Context(context))
  189. if not context:
  190. return t.render(context_instance)
  191. # Add the context to the context stack, ensuring it gets removed again
  192. # to keep the context_instance in the same state it started in.
  193. with context_instance.push(context):
  194. return t.render(context_instance)
  195. def select_template(self, template_name_list, dirs=_dirs_undefined):
  196. """
  197. Given a list of template names, returns the first that can be loaded.
  198. """
  199. if dirs is _dirs_undefined:
  200. # Do not set dirs to None here to avoid triggering the deprecation
  201. # warning in get_template.
  202. pass
  203. else:
  204. warnings.warn(
  205. "The dirs argument of select_template is deprecated.",
  206. RemovedInDjango20Warning, stacklevel=2)
  207. if not template_name_list:
  208. raise TemplateDoesNotExist("No template names provided")
  209. not_found = []
  210. for template_name in template_name_list:
  211. try:
  212. return self.get_template(template_name, dirs)
  213. except TemplateDoesNotExist as exc:
  214. if exc.args[0] not in not_found:
  215. not_found.append(exc.args[0])
  216. continue
  217. # If we get here, none of the templates could be loaded
  218. raise TemplateDoesNotExist(', '.join(not_found))
  219. def compile_string(self, template_string, origin):
  220. """
  221. Compiles template_string into a NodeList ready for rendering.
  222. """
  223. if self.debug:
  224. from .debug import DebugLexer, DebugParser
  225. lexer_class, parser_class = DebugLexer, DebugParser
  226. else:
  227. lexer_class, parser_class = Lexer, Parser
  228. lexer = lexer_class(template_string, origin)
  229. tokens = lexer.tokenize()
  230. parser = parser_class(tokens)
  231. return parser.parse()
  232. def make_origin(self, display_name, loader, name, dirs):
  233. if self.debug and display_name:
  234. # Inner import to avoid circular dependency
  235. from .loader import LoaderOrigin
  236. return LoaderOrigin(display_name, loader, name, dirs)
  237. else:
  238. return None