Overriding Widgets templates not working (following help), template loader not searching app dirs

Using Django 4,
Here it explains how to override a widget template:

Each widget has a template_name attribute with a value such as input.html . Built-in widget templates are stored in the django/forms/widgets path. You can provide a custom template for input.html by defining django/forms/widgets/input.html , for example. See Built-in widgets for the name of each widget’s template.

I.e.:

Step 1. Place folder in correct namespaced dir

And here explains how to perform the next steps:

I.e.
Step 2. Add django.forms to installed apps
Step 3. Set FORM_RENDERER = 'django.forms.renderers.TemplatesSetting' in settings.py

With regards to the above steps:

  1. I placed the overriding template in: <myapp>/templates/django/forms/widgets/input.html
  2. myapp appears before django.forms in INSTALLED_APPS
  3. Here is a snippet from settings.py:
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [my_admin_templates, wc_html],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',  # Adds user to context
                'django.contrib.messages.context_processors.messages',
            ],
            'debug': TEMPLATE_DEBUG,
        },
    },
]

FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'

However after performing the above it does not pick up the overriding template (which has a very obvious change init).

In django/template/loaders/filesystem.py in the method def get_template_sources (line 28), if I print the result of for self.get_dirs(): it prints:

/home/michael/.venv/project/lib/python3.8/site-packages/django/forms/templates
/home/michael/.venv/project/lib/python3.8/site-packages/django/forms/templates

So I can see for sure the app dirs are not being added. I keep checking my paths but they seem correct, don’t know what else to try.

PS, If I try adding a second entry to TEMPLATES in settings.py, e.g.

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [my_admin_templates, wc_html],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [],
            'debug': TEMPLATE_DEBUG,
        },
    },
    {
        'BACKEND': 'django.forms.renderers.TemplatesSetting',
        'APP_DIRS': True,
        'OPTIONS': {
        },
    },
]

FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'

Then it fails with:

File "/home/michael/.venv/project/lib/python3.8/site-packages/django/template/utils.py", line 81, in __getitem__
    engine = engine_cls(params)
TypeError: TemplatesSetting() takes no arguments

You do not need to set the FORM_RENDERER to TemplatesSetting. Leaving the default of DjangoTemplates, along with setting APP_DIRS=True is sufficient.

Either that, or if you’re going to use TemplatesSetting as your engine, then I believe it needs to be configured in your TEMPLATES setting as well. (That’s more “guess” than “knowledge” - I’ve never had to use this at all when overriding templates. The default template search facility has always worked for me.)

Thanks for the advice, hmm that’s weird, cause the docs explicitly say you must use TemplateSetting renderer:

To override widget templates, you must use the TemplatesSetting renderer. Then overriding widget templates works the same as overriding any other template in your project.

Just to confirm are you talking about overriding normal page templates, or widget templates (which in Django 4.0 have seem be rendered by the FORM API and not the usual template renderer)?

I tried not setting FORM_RENDERER and leaving as the default DjangoTemplates renderer, but that didnt work either. not quite sure whats going on.

then I believe it needs to be configured in your TEMPLATES setting as well.

I would think so too. I updated the original question with a comment about what happens when I try to configure the additional template engine

Please provide the page reference where you’re seeing that exact text, because I believe you’re leaving out some context in which that statement may appear.

I know for a fact that it is not something that must be done. I’ve overridden numerous widgets and templates, and have never changed FORM_RENDERER. (I actually hadn’t even been aware of this setting before this conversation.)

Either - doesn’t matter.

Thanks for confirming it works for you without changing FORM_RENDERER. Are you using Django 4.0?

You can find the quote here, thanks!:
https://docs.djangoproject.com/en/4.0/ref/forms/renderers/#templatessetting

In django/forms/renderers.py line 59 it has:

class TemplatesSetting(BaseRenderer):
    """
    Load templates using template.loader.get_template() which is configured
    based on settings.TEMPLATES.
    """
    def get_template(self, template_name):
        return get_template(template_name)

Which agrees with our assumptions, however a new project with:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
    {
        'BACKEND': 'django.forms.renderers.TemplatesSetting',
        'DIRS': [],
        'APP_DIRS': False,
    },
]

Generates the error:


Traceback (most recent call last):
  File "/home/michael/.venv/project/lib/python3.8/site-packages/django/template/utils.py", line 66, in __getitem__
    return self._engines[alias]
KeyError: 'renderers'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/michael/project/src/manage.py", line 16, in <module>
    execute_from_command_line(sys.argv)
  File "/home/michael/.venv/project/lib/python3.8/site-packages/django/core/management/__init__.py", line 425, in execute_from_command_line
    utility.execute()
  File "/home/michael/.venv/project/lib/python3.8/site-packages/django/core/management/__init__.py", line 419, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/michael/.venv/project/lib/python3.8/site-packages/django/core/management/base.py", line 373, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/michael/.venv/project/lib/python3.8/site-packages/django/core/management/base.py", line 417, in execute
    output = self.handle(*args, **options)
  File "/home/michael/.venv/project/lib/python3.8/site-packages/django/core/management/commands/check.py", line 63, in handle
    self.check(
  File "/home/michael/.venv/project/lib/python3.8/site-packages/django/core/management/base.py", line 438, in check
    all_issues = checks.run_checks(
  File "/home/michael/.venv/project/lib/python3.8/site-packages/django/core/checks/registry.py", line 77, in run_checks
    new_errors = check(app_configs=app_configs, databases=databases)
  File "/home/michael/.venv/project/lib/python3.8/site-packages/django/contrib/admin/checks.py", line 78, in check_dependencies
    for engine in engines.all():
  File "/home/michael/.venv/project/lib/python3.8/site-packages/django/template/utils.py", line 90, in all
    return [self[alias] for alias in self]
  File "/home/michael/.venv/project/lib/python3.8/site-packages/django/template/utils.py", line 90, in <listcomp>
    return [self[alias] for alias in self]
  File "/home/michael/.venv/project/lib/python3.8/site-packages/django/template/utils.py", line 81, in __getitem__
    engine = engine_cls(params)
TypeError: TemplatesSetting() takes no arguments

Would you agree this is a bug? If you also think so, then I will create an issue.

I need to verify that for the situation that I’m thinking of regarding widgets - I don’t remember right off-hand if those are among the upgraded projects or not.

I just checked our form handler.

Based on: The Forms API | Django documentation | Django, we handled this by changing the template_name attribute of the form, so it’s not a direct parallel to what you’re doing here.

Thanks for checking your project! I think there is something not quite right with how it is supposed to work.

Well, as documented at Settings | Django documentation | Django, it does not appear that TemplatesSetting is actually a “BACKEND” - so that wouldn’t appear to be where it should be defined. (So my hunch in that area was incorrect.) This may well be true even though it shares a same base class as DjangoTemplates and Jinja2.

So I’m back to where you were at the beginning - a single entry in TEMPLATES with the FORM_RENDERER defined.

1 Like

Hi Ken,

Out of interest (or maybe this will help you one day), got it working as follows (the default django renderer is left untouched, but the project has an additional “Office” style renderer that can be passed into form's renderer argument:


from django.forms.renderers import TemplatesSetting, BaseRenderer
from django.template import engines


class OfficeRenderer(BaseRenderer):
    def get_template(self, template_name):
        # Engine name is a UNIQUE alias in settings.TEMPLATES, if the same backend is used multiple times
        engine = engines['office']
        return engine.get_template(template_name)

    def render(self, template_name, context, request=None):
        """Rendering widgets does not use the context processor, manually add (hackish)"""
        context['gui'] = self.gui
        return super().render(template_name, context)


@functools.lru_cache()
def get_office_renderer():
    """Based on django.forms/renderers.py"""
    renderer = OfficeRenderer()
    return renderer

Context processor:

def add_office_gui(request):
    return { 'gui': 'office' }

With settings.py set to:

import django

# Refer to:  https://docs.djangoproject.com/en/4.0/ref/forms/renderers/#django.forms.renderers.TemplatesSetting
django_form_templates = django.__path__[0] + '/forms/templates'
office_form_templates = Path(CORE_DIR, 'gui/templates_office')   # your own dir here

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [lc_admin_templates, wc_html],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',  # Adds user to context
                'django.contrib.messages.context_processors.messages',
                'core.base.context_processors.linkcube',
                'pwa.context_processors.UnregisterPwaSw',
            ],
            'debug': TEMPLATE_DEBUG,
        },
    },
    {
        'NAME': 'office',
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [office_form_templates, django_form_templates],
        'APP_DIRS': False,
        'OPTIONS': {
            'context_processors': ['core.gui.context_processors.add_office_gui'],
            'debug': TEMPLATE_DEBUG,
        },
    },
}
1 Like