Django Forms ErrorList custom format HTML tags

I want to post this here too to bring more light to this topic:

forms.py

from django import forms
from captcha.fields import CaptchaField, CaptchaTextInput
from django.forms.utils import ErrorList
from django.utils.safestring import mark_safe

class ContactForm(forms.Form):
    first_name = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control', "placeholder": "first name"}), required=True)
    last_name = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control', "placeholder": "last name"}), required=False)
    from_email = forms.EmailField(widget=forms.EmailInput(attrs={'class': 'form-control', "placeholder": "email"}), required=True)
    subject = forms.CharField(widget=forms.TextInput(attrs={'class': 'form-control', "placeholder": "subject"}), required=False)
    message = forms.CharField(widget=forms.Textarea(attrs={'class': 'form-control', "placeholder": "message"}), required=True)
    captcha = CaptchaField(widget=CaptchaTextInput(attrs={'class': 'form-control', "placeholder": "prove u r not a bot 😎"}), required=True, error_messages={'invalid': 'u are a bot 🤖'})

class DivErrorList(ErrorList):
     def __str__(self):
         return self.as_divs()
     def as_divs(self):
         if not self: return ''
         return mark_safe('<div class="errorlist">%s</div>' % ''.join(['<div class="error">%s</div>' % e for e in self]))

views.py

from django.core.mail import send_mail, BadHeaderError
from django.http import HttpResponse
from django.shortcuts import render, redirect
from .forms import ContactForm, DivErrorList
from django.contrib import messages

def contactView(request):
    if request.method == 'GET':
        form = ContactForm()
    else:
        form = ContactForm(request.POST, auto_id=False, error_class=DivErrorList)
        if form.is_valid():
            subject = form.cleaned_data['subject']
            body =  {
                'first_name': form.cleaned_data['first_name'],
                'last_name': form.cleaned_data['last_name'],
                'from_email': form.cleaned_data['from_email'],
                'message': form.cleaned_data['message'],
            }
            message = "\n".join(body.values())

            try:
              send_mail(subject, message, 'hello@testy123.com', ['hello@testy123.com']) 
            except BadHeaderError:
                return HttpResponse('Invalid header found.')
            messages.success(request, 'THANK YOU message received')
            return redirect('home')
		messages.error(request, form.errors)

    return render(request, 'contact.html', {'form': form})

messages.html

{% for message in messages %}

<div class="alert {{ message.tags }} alert-dismissible fade show" role="alert">
  {{ message }}
  <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>

{% endfor %}

image

As you can see, the errors are still being wrapped in a list tag…
I want it to be in a div only…
What am I missing?

You’re not showing enough of the templates being rendered for us to be able to answer that question.

However, it looks like you might be trying to use the messages framework for rendering these form errors - which generally isn’t used for this purpose.

I know what you mean about messages being used for this… but it works without breaking Pythonic rules… messages.error(request, form.errors) from the view DOES work in the template.

Anyways here is the “other way” to do it in the contact template:

<form method="post">
	{% csrf_token %}
	<div class="row">
		<div class="col">
			<p>{{ form.first_name }}</p>
		</div>
		<div class="col">
			<p>{{ form.last_name }}</p>
		</div>
	</div>
		<p>{{ form.from_email }}</p>
		<p>{{ form.subject }}</p>
		<p>{{ form.message }}</p>
		<p>{{ form.captcha }}</p>
		<p>{{ form.errors }}</p>
	<div class="form-actions">
		<button type="submit"
		class="btn btn-info">Send</button>
	</div>
</form>

You can see the highlight showing the bullet from the ul tag (which I do not want).
image

How do I actually customize the class.?

class DivErrorList(ErrorList):
     def __str__(self):
         return self.as_divs()
     def as_divs(self):
         if not self: return ''
         return mark_safe('<div class="errorlist">%s</div>' % ''.join(['<div class="error">%s</div>' % e for e in self]))

Can you explain exactly what is doing on here? I am not great at string manipulation to be honest. THANKS!

image
As you can see the div tag was created but it is still being "wrapped’’ in a list with ul and li tags.

Yes, it does work - no one says it doesn’t. However, those messages are formatted and rendered by the messaging framework and, as you show, end up being enclosed as ul elements because that’s how the messaging framework renders them.

If you don’t want the form errors rendered as an unordered list, then you will need to iterate over that collection yourself and render each entry individually. (In other words, instead of rendering {{ form.errors }} you would iterate over that in a manner like {% for error in form.errors %}{{ error }}....

See the entire docs section and the examples at Rendering fields manually.

I actually got it working that way using the iteration method but I am very curious about how this works…

class DivErrorList(ErrorList):
     def __str__(self):
         return self.as_divs()
     def as_divs(self):
         if not self: return ''
         return mark_safe('<div class="errorlist">%s</div>' % ''.join(['<div class="error">%s</div>' % e for e in self]))

https://docs.djangoproject.com/en/4.1/ref/forms/api/#customizing-the-error-list-format


I want to learn how to do it from the class level. Thank you.

Also in 3.2 it shows this:
https://docs.djangoproject.com/en/3.2/ref/forms/api/#customizing-the-error-list-format

This is what I am really trying to learn.

Notice how in the example you’re highlighting how they’re rendering the entire form using the as_p method. The renderer is accessing the errors from the form object as the ErrorList class.

However, when you specify form.errors, you’re rendering that specific form attribute, which is returned as a dict, specifically, ErrorDict. The default template for rendering that class is to render them as a ul.

If you want to gain a real understanding of how the errors get rendered, I suggest you read the source code in django.forms.utils.

The ErrorList class inherits from RenderableErrorMixin, which inherits from RenderableMixin; using templates defined in django.forms.errors.dict.

1 Like

Thanks for pointing me in the right direction, Ken.
https://docs.djangoproject.com/en/4.1/ref/forms/api/#customizing-the-error-list-format


Looks like this is the accepted way?
Is it worth it? hmm
I have been deep diving into the source files.
But I never tried this.