import functools from collections import Counter from pathlib import Path from django.apps import apps from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.utils.functional import cached_property from django.utils.module_loading import import_string class InvalidTemplateEngineError(ImproperlyConfigured): pass class EngineHandler: def __init__(self, templates=None): """ templates is an optional list of template engine definitions (structured like settings.TEMPLATES). """ self._templates = templates self._engines = {} @cached_property def templates(self): if self._templates is None: self._templates = settings.TEMPLATES templates = {} backend_names = [] for tpl in self._templates: try: # This will raise an exception if 'BACKEND' doesn't exist or # isn't a string containing at least one dot. default_name = tpl['BACKEND'].rsplit('.', 2)[-2] except Exception: invalid_backend = tpl.get('BACKEND', '') raise ImproperlyConfigured( "Invalid BACKEND for a template engine: {}. Check " "your TEMPLATES setting.".format(invalid_backend)) tpl = { 'NAME': default_name, 'DIRS': [], 'APP_DIRS': False, 'OPTIONS': {}, **tpl, } templates[tpl['NAME']] = tpl backend_names.append(tpl['NAME']) counts = Counter(backend_names) duplicates = [alias for alias, count in counts.most_common() if count > 1] if duplicates: raise ImproperlyConfigured( "Template engine aliases aren't unique, duplicates: {}. " "Set a unique NAME for each engine in settings.TEMPLATES." .format(", ".join(duplicates))) return templates def __getitem__(self, alias): try: return self._engines[alias] except KeyError: try: params = self.templates[alias] except KeyError: raise InvalidTemplateEngineError( "Could not find config for '{}' " "in settings.TEMPLATES".format(alias)) # If importing or initializing the backend raises an exception, # self._engines[alias] isn't set and this code may get executed # again, so we must preserve the original params. See #24265. params = params.copy() backend = params.pop('BACKEND') engine_cls = import_string(backend) engine = engine_cls(params) self._engines[alias] = engine return engine def __iter__(self): return iter(self.templates) def all(self): return [self[alias] for alias in self] @functools.lru_cache() def get_app_template_dirs(dirname): """ Return an iterable of paths of directories to load app templates from. dirname is the name of the subdirectory containing templates inside installed applications. """ template_dirs = [ Path(app_config.path) / dirname for app_config in apps.get_app_configs() if app_config.path and (Path(app_config.path) / dirname).is_dir() ] # Immutable return value because it will be cached and shared by callers. return tuple(template_dirs)