Source code for render_static.backends.jinja2
from os.path import normpath
from pathlib import Path
from typing import Dict, Generator, List, Tuple
from django.apps import apps
from django.apps.config import AppConfig
from django.template import TemplateDoesNotExist
from django.template.backends.jinja2 import Jinja2, Template
from jinja2 import Environment
from render_static.loaders.jinja2 import SearchableLoader as SearchableJinja2Loader
from render_static.loaders.jinja2 import StaticFileSystemBatchLoader
from render_static.loaders.mixins import BatchLoaderMixin
from render_static.origin import AppOrigin
from render_static.templatetags import render_static
from .base import StaticEngine
__all__ = ["StaticJinja2Templates"]
def default_env(**options):
"""
The default Jinja2 backend environment. This environment adds the tags
and filters from render_static.
:param options:
:return:
"""
env = Environment(**options)
env.globals.update(render_static.register.filters)
env.globals.update(
{
name: getattr(tag, "__wrapped__", tag)
for name, tag in render_static.register.tags.items()
}
)
return env
[docs]
class StaticJinja2Templates(StaticEngine, Jinja2):
"""
Extend the standard :class:`django.template.backends.jinja2.Jinja2` backend
to add options. Unlike with the standard backend, the loaders used for
this backend remain unchanged.
By default this backend will search for templates in application
directories named ``static_jinja2``. The ``app_dir`` option is added to
the standard option to allow users to override this location.
"""
_app_dirname = "static_jinja2"
@property
def app_dirname(self) -> str:
return self._app_dirname
app_directories: List[Tuple[Path, AppConfig]] = []
[docs]
def __init__(self, params: Dict) -> None:
"""
:param params: The parameters as passed into the :setting:`STATIC_TEMPLATES`
configuration for this backend.
"""
params = params.copy()
self.dirs = list(params.get("DIRS", []))
self.app_dirs = params.get("APP_DIRS", False)
options = params.pop("OPTIONS").copy()
options.setdefault("environment", "render_static.backends.jinja2.default_env")
self._app_dirname = options.pop("app_dir", self.app_dirname)
if "loader" not in options:
options["loader"] = StaticFileSystemBatchLoader(self.template_dirs)
params["OPTIONS"] = options
self.app_directories = [
(Path(app_config.path) / self.app_dirname, app_config)
for app_config in apps.get_app_configs()
if app_config.path and (Path(app_config.path) / self.app_dirname).is_dir()
]
super().__init__(params)
def get_template(self, template_name: str) -> Template:
"""
We override the Jinja2 get_template method so we can monkey patch
in the AppConfig of the origin if this template was from an app
directory. This information is used later down the line when
deciding where to write rendered templates. For the django template
backend we modified the loaders but modifying the Jinja2 loaders
would be much more invasive.
"""
template = super().get_template(template_name)
for app_dir, app in self.app_directories:
if normpath(template.origin.name).startswith(normpath(app_dir)):
template.origin = AppOrigin( # type: ignore
name=template.origin.name,
template_name=template.origin.template_name,
app=app,
)
break
return template
def select_templates(
self,
selector: str,
first_loader: bool = False,
first_preference: bool = False,
) -> List[str]:
"""
Resolves a template selector into a list of template names from
the loader configured on this backend engine.
:param selector: The template selector
:param first_loader: This is ignored for the Jinja2 engine. The
Jinja2 engine only has one loader.
:param first_preference: If true, return only the templates that
match the first preference for the loader. Preferences are
loader specific and documented on the loader.
:return: The list of resolved template names
"""
template_names = set()
if isinstance(self.env.loader, BatchLoaderMixin):
for templates in self.env.loader.select_templates(selector):
if templates:
for tmpl in templates:
template_names.add(tmpl)
if first_preference:
break
else:
self.get_template(selector)
template_names.add(selector)
if template_names:
return list(template_names)
raise TemplateDoesNotExist(
f"Template selector {selector} did not resolve to any template names."
)
def search_templates( # type: ignore[override]
self,
prefix: str,
first_loader: bool = False,
) -> Generator[Template, None, None]:
"""
Resolves a partial template selector into a list of template names from the
loaders configured on this backend engine.
:param prefix: The template prefix to search for
:param first_loader: This is ignored for the Jinja2 engine because there is
only one loader
:return: The list of resolved template names
"""
if isinstance(self.env.loader, SearchableJinja2Loader):
for tmpl in self.env.loader.search(self.env, prefix):
yield Template(tmpl, self)