You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

166 lines
5.5KB

  1. import typing as t
  2. from jinja2 import BaseLoader
  3. from jinja2 import Environment as BaseEnvironment
  4. from jinja2 import Template
  5. from jinja2 import TemplateNotFound
  6. from .globals import _app_ctx_stack
  7. from .globals import _request_ctx_stack
  8. from .signals import before_render_template
  9. from .signals import template_rendered
  10. if t.TYPE_CHECKING:
  11. from .app import Flask
  12. from .scaffold import Scaffold
  13. def _default_template_ctx_processor() -> t.Dict[str, t.Any]:
  14. """Default template context processor. Injects `request`,
  15. `session` and `g`.
  16. """
  17. reqctx = _request_ctx_stack.top
  18. appctx = _app_ctx_stack.top
  19. rv = {}
  20. if appctx is not None:
  21. rv["g"] = appctx.g
  22. if reqctx is not None:
  23. rv["request"] = reqctx.request
  24. rv["session"] = reqctx.session
  25. return rv
  26. class Environment(BaseEnvironment):
  27. """Works like a regular Jinja2 environment but has some additional
  28. knowledge of how Flask's blueprint works so that it can prepend the
  29. name of the blueprint to referenced templates if necessary.
  30. """
  31. def __init__(self, app: "Flask", **options: t.Any) -> None:
  32. if "loader" not in options:
  33. options["loader"] = app.create_global_jinja_loader()
  34. BaseEnvironment.__init__(self, **options)
  35. self.app = app
  36. class DispatchingJinjaLoader(BaseLoader):
  37. """A loader that looks for templates in the application and all
  38. the blueprint folders.
  39. """
  40. def __init__(self, app: "Flask") -> None:
  41. self.app = app
  42. def get_source( # type: ignore
  43. self, environment: Environment, template: str
  44. ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]:
  45. if self.app.config["EXPLAIN_TEMPLATE_LOADING"]:
  46. return self._get_source_explained(environment, template)
  47. return self._get_source_fast(environment, template)
  48. def _get_source_explained(
  49. self, environment: Environment, template: str
  50. ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]:
  51. attempts = []
  52. rv: t.Optional[t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]]
  53. trv: t.Optional[
  54. t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
  55. ] = None
  56. for srcobj, loader in self._iter_loaders(template):
  57. try:
  58. rv = loader.get_source(environment, template)
  59. if trv is None:
  60. trv = rv
  61. except TemplateNotFound:
  62. rv = None
  63. attempts.append((loader, srcobj, rv))
  64. from .debughelpers import explain_template_loading_attempts
  65. explain_template_loading_attempts(self.app, template, attempts)
  66. if trv is not None:
  67. return trv
  68. raise TemplateNotFound(template)
  69. def _get_source_fast(
  70. self, environment: Environment, template: str
  71. ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable]]:
  72. for _srcobj, loader in self._iter_loaders(template):
  73. try:
  74. return loader.get_source(environment, template)
  75. except TemplateNotFound:
  76. continue
  77. raise TemplateNotFound(template)
  78. def _iter_loaders(
  79. self, template: str
  80. ) -> t.Generator[t.Tuple["Scaffold", BaseLoader], None, None]:
  81. loader = self.app.jinja_loader
  82. if loader is not None:
  83. yield self.app, loader
  84. for blueprint in self.app.iter_blueprints():
  85. loader = blueprint.jinja_loader
  86. if loader is not None:
  87. yield blueprint, loader
  88. def list_templates(self) -> t.List[str]:
  89. result = set()
  90. loader = self.app.jinja_loader
  91. if loader is not None:
  92. result.update(loader.list_templates())
  93. for blueprint in self.app.iter_blueprints():
  94. loader = blueprint.jinja_loader
  95. if loader is not None:
  96. for template in loader.list_templates():
  97. result.add(template)
  98. return list(result)
  99. def _render(template: Template, context: dict, app: "Flask") -> str:
  100. """Renders the template and fires the signal"""
  101. before_render_template.send(app, template=template, context=context)
  102. rv = template.render(context)
  103. template_rendered.send(app, template=template, context=context)
  104. return rv
  105. def render_template(
  106. template_name_or_list: t.Union[str, t.List[str]], **context: t.Any
  107. ) -> str:
  108. """Renders a template from the template folder with the given
  109. context.
  110. :param template_name_or_list: the name of the template to be
  111. rendered, or an iterable with template names
  112. the first one existing will be rendered
  113. :param context: the variables that should be available in the
  114. context of the template.
  115. """
  116. ctx = _app_ctx_stack.top
  117. ctx.app.update_template_context(context)
  118. return _render(
  119. ctx.app.jinja_env.get_or_select_template(template_name_or_list),
  120. context,
  121. ctx.app,
  122. )
  123. def render_template_string(source: str, **context: t.Any) -> str:
  124. """Renders a template from the given template source string
  125. with the given context. Template variables will be autoescaped.
  126. :param source: the source code of the template to be
  127. rendered
  128. :param context: the variables that should be available in the
  129. context of the template.
  130. """
  131. ctx = _app_ctx_stack.top
  132. ctx.app.update_template_context(context)
  133. return _render(ctx.app.jinja_env.from_string(source), context, ctx.app)