django.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. from collections import defaultdict
  2. from importlib import import_module
  3. from pkgutil import walk_packages
  4. from django.apps import apps
  5. from django.conf import settings
  6. from django.core.checks import Error, Warning
  7. from django.template import TemplateDoesNotExist
  8. from django.template.context import make_context
  9. from django.template.engine import Engine
  10. from django.template.library import InvalidTemplateLibrary
  11. from .base import BaseEngine
  12. class DjangoTemplates(BaseEngine):
  13. app_dirname = "templates"
  14. def __init__(self, params):
  15. params = params.copy()
  16. options = params.pop("OPTIONS").copy()
  17. options.setdefault("autoescape", True)
  18. options.setdefault("debug", settings.DEBUG)
  19. options.setdefault("file_charset", "utf-8")
  20. libraries = options.get("libraries", {})
  21. options["libraries"] = self.get_templatetag_libraries(libraries)
  22. super().__init__(params)
  23. self.engine = Engine(self.dirs, self.app_dirs, **options)
  24. def check(self, **kwargs):
  25. return [
  26. *self._check_string_if_invalid_is_string(),
  27. *self._check_for_template_tags_with_the_same_name(),
  28. ]
  29. def _check_string_if_invalid_is_string(self):
  30. value = self.engine.string_if_invalid
  31. if not isinstance(value, str):
  32. return [
  33. Error(
  34. "'string_if_invalid' in TEMPLATES OPTIONS must be a string but "
  35. "got: %r (%s)." % (value, type(value)),
  36. obj=self,
  37. id="templates.E002",
  38. )
  39. ]
  40. return []
  41. def _check_for_template_tags_with_the_same_name(self):
  42. libraries = defaultdict(set)
  43. for module_name, module_path in get_template_tag_modules():
  44. libraries[module_name].add(module_path)
  45. for module_name, module_path in self.engine.libraries.items():
  46. libraries[module_name].add(module_path)
  47. errors = []
  48. for library_name, items in libraries.items():
  49. if len(items) > 1:
  50. items = ", ".join(repr(item) for item in sorted(items))
  51. errors.append(
  52. Warning(
  53. f"{library_name!r} is used for multiple template tag modules: "
  54. f"{items}",
  55. obj=self,
  56. id="templates.W003",
  57. )
  58. )
  59. return errors
  60. def from_string(self, template_code):
  61. return Template(self.engine.from_string(template_code), self)
  62. def get_template(self, template_name):
  63. try:
  64. return Template(self.engine.get_template(template_name), self)
  65. except TemplateDoesNotExist as exc:
  66. reraise(exc, self)
  67. def get_templatetag_libraries(self, custom_libraries):
  68. """
  69. Return a collation of template tag libraries from installed
  70. applications and the supplied custom_libraries argument.
  71. """
  72. libraries = get_installed_libraries()
  73. libraries.update(custom_libraries)
  74. return libraries
  75. class Template:
  76. def __init__(self, template, backend):
  77. self.template = template
  78. self.backend = backend
  79. @property
  80. def origin(self):
  81. return self.template.origin
  82. def render(self, context=None, request=None):
  83. context = make_context(
  84. context, request, autoescape=self.backend.engine.autoescape
  85. )
  86. try:
  87. return self.template.render(context)
  88. except TemplateDoesNotExist as exc:
  89. reraise(exc, self.backend)
  90. def copy_exception(exc, backend=None):
  91. """
  92. Create a new TemplateDoesNotExist. Preserve its declared attributes and
  93. template debug data but discard __traceback__, __context__, and __cause__
  94. to make this object suitable for keeping around (in a cache, for example).
  95. """
  96. backend = backend or exc.backend
  97. new = exc.__class__(*exc.args, tried=exc.tried, backend=backend, chain=exc.chain)
  98. if hasattr(exc, "template_debug"):
  99. new.template_debug = exc.template_debug
  100. return new
  101. def reraise(exc, backend):
  102. """
  103. Reraise TemplateDoesNotExist while maintaining template debug information.
  104. """
  105. new = copy_exception(exc, backend)
  106. raise new from exc
  107. def get_template_tag_modules():
  108. """
  109. Yield (module_name, module_path) pairs for all installed template tag
  110. libraries.
  111. """
  112. candidates = ["django.templatetags"]
  113. candidates.extend(
  114. f"{app_config.name}.templatetags" for app_config in apps.get_app_configs()
  115. )
  116. for candidate in candidates:
  117. try:
  118. pkg = import_module(candidate)
  119. except ImportError:
  120. # No templatetags package defined. This is safe to ignore.
  121. continue
  122. if hasattr(pkg, "__path__"):
  123. for name in get_package_libraries(pkg):
  124. yield name.removeprefix(candidate).lstrip("."), name
  125. def get_installed_libraries():
  126. """
  127. Return the built-in template tag libraries and those from installed
  128. applications. Libraries are stored in a dictionary where keys are the
  129. individual module names, not the full module paths. Example:
  130. django.templatetags.i18n is stored as i18n.
  131. """
  132. return {
  133. module_name: full_name for module_name, full_name in get_template_tag_modules()
  134. }
  135. def get_package_libraries(pkg):
  136. """
  137. Recursively yield template tag libraries defined in submodules of a
  138. package.
  139. """
  140. for entry in walk_packages(pkg.__path__, pkg.__name__ + "."):
  141. try:
  142. module = import_module(entry[1])
  143. except ImportError as e:
  144. raise InvalidTemplateLibrary(
  145. "Invalid template library specified. ImportError raised when "
  146. "trying to load '%s': %s" % (entry[1], e)
  147. ) from e
  148. if hasattr(module, "register"):
  149. yield entry[1]