#35988: ErrorDict always uses default renderer
----------------------------------------+------------------------------
Reporter: Adam Johnson | Owner: Adam Johnson
Type: Bug | Status: assigned
Component: Forms | Version: dev
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 0
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
----------------------------------------+------------------------------
When `BaseForm.full_clean()` instantiates `ErrorDict`, it doesn't pass it
the `renderer`:
https://github.com/django/django/blob/1860a1afc9ac20750f932e8e0a94b32d096f2848/django/forms/forms.py#L319
Despite `ErrorDict` being ready to receive it and use it for rendering:
https://github.com/django/django/blob/1860a1afc9ac20750f932e8e0a94b32d096f2848/django/forms/utils.py#L124
Practically, this means customizations to the renderer are ignored when
rendering the form errors using `{{ errors }}` in the template.
Building on top of the example and fix from #35987, I have a custom
renderer that swaps out some templates:
{{{#!python
from django import forms
from django.forms.renderers import TemplatesSetting
from django.forms.utils import ErrorList
from django.template.exceptions import TemplateDoesNotExist
class CustomRenderer(TemplatesSetting):
def get_template(self, template_name):
if template_name.startswith("django/forms/"):
# Load our custom version from "custom/forms/" if it exists
our_template =
f"custom/{template_name.removeprefix('django/')}"
try:
return super().get_template(our_template)
except TemplateDoesNotExist:
pass
return super().get_template(template_name)
class CustomErrorList(ErrorList):
def copy(self):
# Copying the fix from Django Ticket #35987
copy = super().copy()
copy.renderer = self.renderer
return copy
class MyForm(forms.Form):
default_renderer = CustomRenderer()
def __init__(self, *args, error_class=CustomErrorList, **kwargs):
super().__init__(*args, error_class=error_class, **kwargs)
}}}
The custom error list template uses some CSS utility classes from
Tailwind, like `text-red-600`:
{{{#!htmldjango
{% if errors %}<ul class="text-red-600">{% for field, error in errors
%}<li>{{ field }}{{ error }}</li>{% endfor %}</ul>{% endif %}
}}}
But creating a form with a non-field error and rendering the error dict
uses the default renderer and its template:
{{{
In [1]: from example.forms import MyForm
...:
...: form = MyForm({})
...: form.full_clean()
...: form.add_error(None, "Test error")
In [2]: form.errors.render()
Out[2]: '<ul class="errorlist"><li>__all__<ul class="text-
red-600"><li>Test error</li></ul></li></ul>'
}}}
I need to override `full_clean()` to set the renderer:
{{{#!python
class MyForm(forms.Form):
...
def full_clean(self):
super().full_clean()
# Fix a bug in Django where self._errors = ErrorDict is not passed
the
# renderer argument when initialized.
self._errors.renderer = self.renderer
}}}
Then form errors use my custom template:
{{{
In [1]: from example.forms import MyForm
...:
...: form = MyForm({})
...: form.full_clean()
...: form.add_error(None, "Test error")
In [2]: form.errors.render()
Out[2]: '<ul class="text-red-600"><li>__all__<ul class="text-
red-600"><li>Test error</li></ul></li></ul>'
}}}
I think this has probably been an issue ever since a custom renderer
became possible in #31026. The argument was added to `ErrorDict` but
missed in `BaseForm.full_clean()`, the only place where the class is
instantiated.
--
Ticket URL: <https://code.djangoproject.com/ticket/35988>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/django-updates/01070193ab1e170f-6ca73716-6d22-4830-912b-3c8398fe2c08-000000%40eu-central-1.amazonses.com.