[Django] #35988: ErrorDict always uses default renderer

10 views
Skip to first unread message

Django

unread,
Dec 9, 2024, 6:11:41 AM12/9/24
to django-...@googlegroups.com
#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.

Django

unread,
Dec 9, 2024, 6:18:15 AM12/9/24
to django-...@googlegroups.com
#35988: ErrorDict always uses default renderer
------------------------------+----------------------------------------
Reporter: Adam Johnson | Owner: Adam Johnson
Type: Bug | Status: assigned
Component: Forms | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Unreviewed
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+----------------------------------------
Changes (by Adam Johnson):

* has_patch: 0 => 1

--
Ticket URL: <https://code.djangoproject.com/ticket/35988#comment:1>

Django

unread,
Dec 9, 2024, 6:38:53 AM12/9/24
to django-...@googlegroups.com
#35988: ErrorDict always uses default renderer
------------------------------+----------------------------------------
Reporter: Adam Johnson | Owner: Adam Johnson
Type: Bug | Status: assigned
Component: Forms | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------------+----------------------------------------
Changes (by Sarah Boyce):

* stage: Unreviewed => Accepted

--
Ticket URL: <https://code.djangoproject.com/ticket/35988#comment:2>

Django

unread,
Dec 9, 2024, 6:53:36 AM12/9/24
to django-...@googlegroups.com
#35988: ErrorDict always uses default renderer
-------------------------------------+-------------------------------------
Reporter: Adam Johnson | Owner: Adam
| Johnson
Type: Bug | Status: assigned
Component: Forms | Version: dev
Severity: Normal | Resolution:
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Sarah Boyce):

* stage: Accepted => Ready for checkin

--
Ticket URL: <https://code.djangoproject.com/ticket/35988#comment:3>

Django

unread,
Dec 10, 2024, 6:13:59 AM12/10/24
to django-...@googlegroups.com
#35988: ErrorDict always uses default renderer
-------------------------------------+-------------------------------------
Reporter: Adam Johnson | Owner: Adam
| Johnson
Type: Bug | Status: closed
Component: Forms | Version: dev
Severity: Normal | Resolution: fixed
Keywords: | Triage Stage: Ready for
| checkin
Has patch: 1 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Sarah Boyce <42296566+sarahboyce@…>):

* resolution: => fixed
* status: assigned => closed

Comment:

In [changeset:"02628c051c18d000256424daffe996c22bed5ae3" 02628c05]:
{{{#!CommitTicketReference repository=""
revision="02628c051c18d000256424daffe996c22bed5ae3"
Fixed #35988 -- Made BaseForm.full_clean() pass renderer to ErrorDict.
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/35988#comment:4>
Reply all
Reply to author
Forward
0 new messages