Customizing newforms

You like newforms but are a little bit uncertain how to style your forms using CSS with form.as_ul? Well, if you want more CSS classes to style upon, here a simple way on how to get them the easy way.

This mini-tutorial will show you how to add CSS classes to the help text and position the errorlist differently for fields in a newform-Form rendered with as_p() (although this also should apply to all the other rendering methods).


First of all a short look at the current implementation of the as_p() method of the BaseForm class as defined in django.newforms.forms:

def as_p(self):
    "Returns this form rendered as HTML <p>s."
    return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>',
		u'<p>%s</p>', '</p>', u' %s', True)

So if we want to change this, just create a subclass of the Form class (which itself is a subclass to BaseForm) somewhere in your code - let’s say in myproj.myapp.forms for now:

import django.newforms as forms

class BaseForm(forms.Form):
	def as_p(self):
		return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>',
			u'<p>%s</p>', '</p>', u' %s', True)

To get more detailed output, all we have to do, is change the parameters passed to self._html_output(..), so I will skip the rest from now on.

This method offers parameters for everything we will need from now on:

normal_row
Here you can set the base format for the field rendering.
error_row
This argument allows you to style the "wrapper" for the errorlist and will only get used if really an error affecting the current field appears and it will only be used if the errorlist should get its own row (as can be set with the last argument).
row_ender
This is only used internally, so we will skip this.
help_text_html
Formating the help output which only gets used if there is any help_text to apply it on ;-)
errors_on_separate_row
Remember when I said error_row is a little bit misleading? Well, this only really applied when you set this parameter to False ;-) Then you will have to put "%(errors)s" somewhere into your normal_row format in order to get any error rendering at all :-)

So let’s apply this intel: First of all, it would be handy, if the help_text of each form field had it’s own class, so we could easily ban it to its own line using display:block if we want to:

return self._html_output(u'<p>%(label)s %(field)s<span class="help">%(help_text)s</span></p>',
	u'<p>%s</p>', '</p>', u' %s', True)

This is the dirty way of doing it, since it will create that span even if the actual help text is empty. To get around this, you can change the help_text_html argument:

return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>',
	u'<p>%s</p>', '</p>', u'<span class="help">%s</span>', True)

So this will make styling the help text way easier. But one big problem for me still remained: The error messages for each field were flying around like crazy. That’s because the argument that is set to True here (errors_on_separate_row) actually tells the method, to put the error messages to a field on their own rows, or in this case in their own paragraphs. Since I don’t like that, I will just switch it to False, which will put the error list right into the same paragraph with the rest of the field’s output. If you do this, you will also have to add the errors to the first argument:

u'<p>%(label)s %(field)s%(errors)s%(help_text)s</p>', u'<p>%s</p>'

The error_row content can stay the same, since the error list already comes with the “errorlist” class by default :-)