summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAymeric Augustin <aymeric.augustin@m4x.org>2014-11-11 16:32:19 +0100
committerAymeric Augustin <aymeric.augustin@m4x.org>2014-12-28 16:08:31 +0100
commit1acfd624d60160e00c9cef113b9d8af65d6b2a72 (patch)
treedfed46696aeb1cda976bd98a0e2405eb1fd3888a
parentb19693e6d8a509eaea282d250310b4ce3e94fe8d (diff)
Added initial support for loading template engines.
-rw-r--r--django/conf/global_settings.py2
-rw-r--r--django/conf/project_template/project_name/settings.py20
-rw-r--r--django/template/__init__.py14
-rw-r--r--django/template/utils.py98
-rw-r--r--django/test/signals.py11
-rw-r--r--docs/ref/settings.txt87
6 files changed, 230 insertions, 2 deletions
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 5fd9029325..a950cfd565 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -230,6 +230,8 @@ TEMPLATE_CONTEXT_PROCESSORS = (
# Output to use in template system for invalid (e.g. misspelled) variables.
TEMPLATE_STRING_IF_INVALID = ''
+TEMPLATES = []
+
# Default email address to use for various automated correspondence from
# the site managers.
DEFAULT_FROM_EMAIL = 'webmaster@localhost'
diff --git a/django/conf/project_template/project_name/settings.py b/django/conf/project_template/project_name/settings.py
index 827822e1ff..33b08fa2a9 100644
--- a/django/conf/project_template/project_name/settings.py
+++ b/django/conf/project_template/project_name/settings.py
@@ -53,6 +53,25 @@ MIDDLEWARE_CLASSES = (
ROOT_URLCONF = '{{ project_name }}.urls'
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.core.context_processors.debug',
+ 'django.core.context_processors.i18n',
+ 'django.core.context_processors.tz',
+ 'django.core.context_processors.media',
+ 'django.core.context_processors.static',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
WSGI_APPLICATION = '{{ project_name }}.wsgi.application'
@@ -66,6 +85,7 @@ DATABASES = {
}
}
+
# Internationalization
# https://docs.djangoproject.com/en/{{ docs_version }}/topics/i18n/
diff --git a/django/template/__init__.py b/django/template/__init__.py
index 24a51ac20d..31f3078bad 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -1,3 +1,15 @@
+### Multiple Template Engines
+
+from .utils import EngineHandler
+
+
+engines = EngineHandler()
+
+__all__ = ('engines',)
+
+
+### Django Template Language
+
# Public exceptions
from .base import (TemplateDoesNotExist, TemplateSyntaxError, # NOQA
VariableDoesNotExist)
@@ -14,4 +26,4 @@ from .base import resolve_variable # NOQA
from .base import Library # NOQA
-__all__ = ('Template', 'Context', 'RequestContext')
+__all__ += ('Template', 'Context', 'RequestContext')
diff --git a/django/template/utils.py b/django/template/utils.py
index 48e4f9abed..3303c1ccb7 100644
--- a/django/template/utils.py
+++ b/django/template/utils.py
@@ -1,9 +1,107 @@
+from collections import Counter, OrderedDict
import os
import sys
+import warnings
from django.apps import apps
+from django.conf import settings
+from django.core.exceptions import ImproperlyConfigured
from django.utils import lru_cache
from django.utils import six
+from django.utils.deprecation import RemovedInDjango20Warning
+from django.utils.functional import cached_property
+from django.utils.module_loading import import_string
+
+
+class InvalidTemplateEngineError(ImproperlyConfigured):
+ pass
+
+
+class EngineHandler(object):
+ 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
+
+ if not self._templates:
+ warnings.warn(
+ "You haven't defined a TEMPLATES setting. You must do so "
+ "before upgrading to Django 2.0. Otherwise Django will be "
+ "unable to load templates.", RemovedInDjango20Warning)
+ self._templates = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': settings.TEMPLATE_DIRS,
+ 'OPTIONS': {
+ 'allowed_include_roots': settings.ALLOWED_INCLUDE_ROOTS,
+ 'context_processors': settings.TEMPLATE_CONTEXT_PROCESSORS,
+ 'loaders': settings.TEMPLATE_LOADERS,
+ 'string_if_invalid': settings.TEMPLATE_STRING_IF_INVALID,
+ },
+ },
+ ]
+
+ templates = OrderedDict()
+ for tpl in self._templates:
+ tpl = tpl.copy()
+ 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', '<not defined>')
+ raise ImproperlyConfigured(
+ "Invalid BACKEND for a template engine: {}. Check "
+ "your TEMPLATES setting.".format(invalid_backend))
+
+ tpl.setdefault('NAME', default_name)
+ tpl.setdefault('DIRS', [])
+ tpl.setdefault('APP_DIRS', False)
+ tpl.setdefault('OPTIONS', {})
+
+ templates[tpl['NAME']] = tpl
+
+ counts = Counter(list(templates))
+ 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))
+
+ 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]
@lru_cache.lru_cache()
diff --git a/django/test/signals.py b/django/test/signals.py
index d8a864d1f4..6888753b06 100644
--- a/django/test/signals.py
+++ b/django/test/signals.py
@@ -84,8 +84,9 @@ def clear_routers_cache(**kwargs):
@receiver(setting_changed)
-def reset_default_template_engine(**kwargs):
+def reset_template_engines(**kwargs):
if kwargs['setting'] in {
+ 'TEMPLATES',
'TEMPLATE_DIRS',
'ALLOWED_INCLUDE_ROOTS',
'TEMPLATE_CONTEXT_PROCESSORS',
@@ -93,7 +94,15 @@ def reset_default_template_engine(**kwargs):
'TEMPLATE_LOADERS',
'TEMPLATE_STRING_IF_INVALID',
'FILE_CHARSET',
+ 'INSTALLED_APPS',
}:
+ from django.template import engines
+ try:
+ del engines.templates
+ except AttributeError:
+ pass
+ engines._templates = None
+ engines._engines = {}
from django.template.engine import Engine
Engine.get_default.cache_clear()
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index d5dc447d8d..7152b5eed9 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -2288,6 +2288,92 @@ will still be printed, but will not prevent management commands from running.
See also the :doc:`/ref/checks` documentation.
+.. setting:: TEMPLATES
+
+TEMPLATES
+---------
+
+.. versionadded:: 1.8
+
+Default:: ``[]`` (Empty list)
+
+A list containing the settings for all template engines to be used with
+Django. Each item of the list is a dictionary containing the options for an
+individual engine.
+
+Here's a simple setup that tells the Django template engine to load templates
+from the ``templates`` subdirectories inside installed applications::
+
+ TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'APP_DIRS': True,
+ },
+ ]
+
+The following options are available for all backends.
+
+.. setting:: TEMPLATES-BACKEND
+
+BACKEND
+~~~~~~~
+
+Default: not defined
+
+The template backend to use. The built-in template backends are:
+
+* ``'django.template.backends.django.DjangoTemplates'``
+* ``'django.template.backends.jinja2.Jinja2'``
+
+You can use a template backend that doesn't ship with Django by setting
+``BACKEND`` to a fully-qualified path (i.e. ``'mypackage.whatever.Backend'``).
+
+.. setting:: TEMPLATES-NAME
+
+NAME
+~~~~
+
+Default: see below
+
+The alias for this particular template engine. It's an identifier that allows
+selecting an engine for rendering. Aliases must be unique across all
+configured template engines.
+
+It defaults to the name of the module defining the engine class, i.e. the
+next to last piece of :setting:`BACKEND <TEMPLATES-BACKEND>`, when it isn't
+provided. For example if the backend is ``'mypackage.whatever.Backend'`` then
+its default name is ``'whatever'``.
+
+.. setting:: TEMPLATES-DIRS
+
+DIRS
+~~~~
+
+Default:: ``[]`` (Empty list)
+
+Directories where the engine should look for template source files, in search
+order.
+
+.. setting:: TEMPLATES-APP_DIRS
+
+APP_DIRS
+~~~~~~~~
+
+Default:: ``False``
+
+Whether the engine should look for template source files inside installed
+applications.
+
+.. setting:: TEMPLATES-OPTIONS
+
+OPTIONS
+~~~~~~~
+
+Default:: ``{}`` (Empty dict)
+
+Extra parameters to pass to the template backend. Available parameters vary
+depending on the template backend.
+
.. setting:: TEMPLATE_CONTEXT_PROCESSORS
TEMPLATE_CONTEXT_PROCESSORS
@@ -3327,6 +3413,7 @@ Serialization
Templates
---------
* :setting:`ALLOWED_INCLUDE_ROOTS`
+* :setting:`TEMPLATES`
* :setting:`TEMPLATE_CONTEXT_PROCESSORS`
* :setting:`TEMPLATE_DEBUG`
* :setting:`TEMPLATE_DIRS`