From 6b3724fa1116c0949a6e0cd3e0dd55959a3abd93 Mon Sep 17 00:00:00 2001 From: Carlton Gibson <carlton.gibson@noumenal.co.uk> Date: Fri, 10 Mar 2017 21:46:37 +0100 Subject: [PATCH] Fixed #27359 -- Made Engine.get_default() return the first DjangoTemplates engine if multiple are defined. --- AUTHORS | 1 + django/template/engine.py | 21 ++++++--------------- docs/ref/templates/api.txt | 20 ++++++++++++++------ docs/releases/2.0.txt | 4 +++- tests/template_tests/test_engine.py | 6 +++--- 5 files changed, 27 insertions(+), 25 deletions(-) diff --git a/AUTHORS b/AUTHORS index 5524ea9ca4..0ab2376643 100644 --- a/AUTHORS +++ b/AUTHORS @@ -141,6 +141,7 @@ answer newbie questions, and generally made Django that much better: Carl Meyer <carl@oddbird.net> Carlos Eduardo de Paula <carlosedp@gmail.com> Carlos MatÃas de la Torre <cmdelatorre@gmail.com> + Carlton Gibson <carlton.gibson@noumenal.es> cedric@terramater.net ChaosKCW Charlie Leifer <coleifer@gmail.com> diff --git a/django/template/engine.py b/django/template/engine.py index e10e8f0078..381d656bbb 100644 --- a/django/template/engine.py +++ b/django/template/engine.py @@ -56,9 +56,8 @@ class Engine: @functools.lru_cache() def get_default(): """ - When only one DjangoTemplates backend is configured, return it. - - Raise ImproperlyConfigured otherwise. + Return the first DjangoTemplates backend that's configured, or raise + ImproperlyConfigured if none are configured. This is required for preserving historical APIs that rely on a globally available, implicitly configured engine such as: @@ -74,18 +73,10 @@ class Engine: # local imports are required to avoid import loops. from django.template import engines from django.template.backends.django import DjangoTemplates - django_engines = [engine for engine in engines.all() - if isinstance(engine, DjangoTemplates)] - if len(django_engines) == 1: - # Unwrap the Engine instance inside DjangoTemplates - return django_engines[0].engine - elif len(django_engines) == 0: - raise ImproperlyConfigured( - "No DjangoTemplates backend is configured.") - else: - raise ImproperlyConfigured( - "Several DjangoTemplates backends are configured. " - "You must select one explicitly.") + for engine in engines.all(): + if isinstance(engine, DjangoTemplates): + return engine.engine + raise ImproperlyConfigured('No DjangoTemplates backend is configured.') @cached_property def template_context_processors(self): diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index 2efbedbdc2..e8d140910d 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -146,14 +146,20 @@ what's passed by :class:`~django.template.backends.django.DjangoTemplates`. .. staticmethod:: Engine.get_default() - When a Django project configures one and only one - :class:`~django.template.backends.django.DjangoTemplates` engine, this - method returns the underlying :class:`Engine`. In other circumstances it - will raise :exc:`~django.core.exceptions.ImproperlyConfigured`. + Returns the underlying :class:`Engine` from the first configured + :class:`~django.template.backends.django.DjangoTemplates` engine. Raises + :exc:`~django.core.exceptions.ImproperlyConfigured` if no engines are + configured. It's required for preserving APIs that rely on a globally available, implicitly configured engine. Any other use is strongly discouraged. + .. versionchanged:: 2.0 + + In older versions, raises + :exc:`~django.core.exceptions.ImproperlyConfigured` if multiple + engines are configured rather than returning the first engine. + .. method:: Engine.from_string(template_code) Compiles the given template code and returns a :class:`Template` object. @@ -175,9 +181,11 @@ The recommended way to create a :class:`Template` is by calling the factory methods of the :class:`Engine`: :meth:`~Engine.get_template`, :meth:`~Engine.select_template` and :meth:`~Engine.from_string`. -In a Django project where the :setting:`TEMPLATES` setting defines exactly one +In a Django project where the :setting:`TEMPLATES` setting defines a :class:`~django.template.backends.django.DjangoTemplates` engine, it's -possible to instantiate a :class:`Template` directly. +possible to instantiate a :class:`Template` directly. If more than one +:class:`~django.template.backends.django.DjangoTemplates` engine is defined, +the first one will be used. .. class:: Template diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt index 369a5893ba..ee2117546b 100644 --- a/docs/releases/2.0.txt +++ b/docs/releases/2.0.txt @@ -191,7 +191,9 @@ Signals Templates ~~~~~~~~~ -* ... +* To increase the usefulness of :meth:`.Engine.get_default` in third-party + apps, it now returns the first engine if multiple ``DjangoTemplates`` engines + are configured in ``TEMPLATES`` rather than raising ``ImproperlyConfigured``. Tests ~~~~~ diff --git a/tests/template_tests/test_engine.py b/tests/template_tests/test_engine.py index 2ed28e6ce4..2bb8601fbb 100644 --- a/tests/template_tests/test_engine.py +++ b/tests/template_tests/test_engine.py @@ -41,14 +41,14 @@ class GetDefaultTests(SimpleTestCase): @override_settings(TEMPLATES=[{ 'NAME': 'default', 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': {'file_charset': 'abc'}, }, { 'NAME': 'other', 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'OPTIONS': {'file_charset': 'def'}, }]) def test_multiple_engines_configured(self): - msg = 'Several DjangoTemplates backends are configured. You must select one explicitly.' - with self.assertRaisesMessage(ImproperlyConfigured, msg): - Engine.get_default() + self.assertEqual(Engine.get_default().file_charset, 'abc') class LoaderTests(SimpleTestCase):