#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.

Reply via email to