Feature Request: get_default_backend() in django.template.Engine

112 views
Skip to first unread message

Peter Thomassen

unread,
Aug 11, 2022, 2:44:06 PM8/11/22
to django-d...@googlegroups.com
Hi,

We're trying to create a template from string using DjangoTemplates.from_string() of the default template backend, which wraps the result of Engine.from_string() to construct an instance of the Template class defined in the backend definition file: django/template/backends/django.py#30

This Template class differs from the Engine-delivered Template in that it accepts a dictionary as the context, see https://docs.djangoproject.com/en/4.0/topics/templates/#django.template.backends.base.Template.render.

The default backend's engine is easily retrieved via django.template.Engine.get_default(). This function identifies the first configured DjangoTemplates backend, and then returns its .engine attribute. As a result, when from_string() is called, it will be called on the engine and not on the backend, thus the wrapping Template() call is missing.

I would like to propose adding a get_default_backend() function to django.template.Engine, which is like get_default(), but returns the backend itself (and not its .engine attribute). get_default() can then be adjusted to simply return get_default_backend().engine.

While trying to solve the issue (context argument type mismatch: dict vs Context), I found the docs not very helpful, as they are not very accurate in the distinction between a backend and its engine. I've also added a commit that clarifies that language.

I have submitted a patch here: https://github.com/django/django/pull/15944


FWIW, the problem can also be solved on the application layer, like:

def get_default_template_backend():
# Ad-hoc implementation of https://github.com/django/django/pull/15944
from django.template import engines
for backend in engines.all():
if isinstance(backend, DjangoTemplates):
return backend
raise ImproperlyConfigured("No DjangoTemplates backend is configured.")

However and IMHO, this needlessly duplicates the logic from get_default() and adds undue complication to the app (like having to know about and looping over the .all() iterable; raising the exception), whereas that complexity already is in the core.


Please let me know what you think!

Thanks,
Peter

--
https://desec.io/

Carlton Gibson

unread,
Aug 22, 2022, 6:21:10 AM8/22/22
to Django developers (Contributions to Django itself)
Hi Peter. 

I agree with you that the documentation isn't that clear here. "Why are there two template classes?", "Why does one take a dict and the other a Context?" — that comes up. 🙂

I don't think your suggestion itself it that unreasonable, but I'm wary about expanding the API here, at least before exploring other options. 

The current get_default() behaviour was only added to allow folks manually creating `Template("My String")` instances to continue to operate with multiple Django backends defined (#27359) — the initial assumption there was that anonymous backend usage wouldn't be supported, i.e. that folks would do `engines["django"]` to get a specific backend.  Could you use that? 

If you've got a traditional Template (expecting a Context) can't you just provide one, via the make_context helper if needed? 
You already know here that you want a DjangoTemplate backend, we know we get that, so why the need for backend-neutral dict-taking Template? 
(It doesn't look like we need that?)

I guess, can you explain more about how the use-case comes up here? #27359 was a while back now so we might exposing a new hook to have come up more quickly. 

Thanks. 

Kind Regards,

Carlton

Peter Thomassen

unread,
Aug 23, 2022, 3:40:02 PM8/23/22
to django-d...@googlegroups.com
Hi Carlton,

On Mon, Aug 22, 2022 at 6:21 AM Carlton Gibson <carlton...@gmail.com> wrote:
> The current get_default() behaviour was only added to allow folks manually creating `Template("My String")` instances to continue to operate with multiple Django backends defined (#27359) — the initial assumption there was that anonymous backend usage wouldn't be supported, i.e. that folks would do `engines["django"]` to get a specific backend. Could you use that?

Yeah, that works! Thank you for the hint.

The docs mention this functionality with the wrong wording, which is why I was not aware of this functionality:

> Template engines are available in django.template.engines:
>
> from django.template import engines
>
> django_engine = engines['django']
> template = django_engine.from_string("Hello {{ name }}!")
-- https://docs.djangoproject.com/en/4.0/topics/templates/#django.template.loader.engines

This is not accurate: `engines` ironically holds the template backends, not the engines, and each engine can be retrieved by calling .engine (such as engines['django'].engine).

In general, the docs are very inconsistent in this regard and use the terms "backend" and "engine" pretty much interchangeably. The code is naturally more strict, but the iterable holding the backends nevertheless is called `engines`.

If people feel this is a correct assessment, I can try improving on the wording.

Adam Johnson

unread,
Aug 23, 2022, 4:50:03 PM8/23/22
to Django developers (Contributions to Django itself)
Improving the docs sounds good.

I think it would also be worth renaming engines to backends, and EngineHandler to BackendHandler, or similar. Yes it would require a deprecation cycle, but I also imagine 99% of projects would be unaffected.

--
You received this message because you are subscribed to the Google Groups "Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/5232c741-8410-1bcc-62d0-3ee942b25ea2%40desec.io.
Reply all
Reply to author
Forward
0 new messages