Displaying Django form field help text in a Bootstrap 3 Popover

Bootstrap and Django make a great combination; but sometimes it’s a little tricky to integrate them in a neat way.

I like to display form field help text in a tooltip-like element in my web forms. In Django models, help text can be defined as a field attribute called help_text. I want this text to appear in a tooltip when the user hovers the mouse over the form field.

Previously, I’ve used the amazing and very powerful qTip2 for this, but since I’ve already got the Bootstrap libraries in my project, which come with a good tooltip plugin called Popover, I figured that I could get by without including another javascript library. Displaying help text is a simple function that doesn’t require the advanced customisability of qTip2.

The Bootstrap 3 popover can read the following attributes present on the element the popover is attached to:

  • data-container="body": The popover will function without this, but it might display weirdly, so better to include it.
  • data-toggle="popover": This is not essential if you bind the popover to the HTML class as I’m doing below.
  • data-placement="left", or right, top, or bottom. Determines where the popover appears in relation to the anchor element.
  • data-content="The text you want to appear in the popover."

In order for these elements to get into the HTML of the Django form field, we need to modify the attrs attribute of the field’s widget. Here’s how you do it in the form class:

class ItemForm(forms.ModelForm):
    class Meta:
        model = Item

    def __init__(self, *args, **kwargs):
        super(ItemForm, self).__init__(*args, **kwargs)
        for field in self.fields:
            help_text = self.fields[field].help_text
            self.fields[field].help_text = None
            if help_text != '':
                self.fields[field].widget.attrs.update({'class':'has-popover', 'data-content':help_text, 'data-placement':'right', 'data-container':'body'})

As you can see, after calling the __init__ method of the parent class, we loop through the fields in the form and assign the help text to a local variable. I’ve then chosen to assign the help_text attribute on the field to None, because I don’t want it showing up in my form elsewhere, but you may want to keep it, particularly if you are customising your form’s HTML in the template. For those fields that had some text in their help_text attribute, we then update the widget’s HTML attributes to include the data necessary for the popover to function. The has-popover class gets added so that we can identify these elements on the template and initialise the popover javascript on them.

The javascript in the template then looks like this (of course the bootstrap javascript library has already been called somewhere):

$(document).ready(function() {
	$('.has-popover').popover({'trigger':'hover'});
});

I want my popovers to appear on hover, rather than on click which is the default, so I’ve specified that as an option in the popover initialisation.

And there it is – you should now have a functional bootstrap popover on your form field.
Here’s mine:
popover-grab

Note – this works for a model form, but in a normal form, you could easily specify the same attributes in your manually described field instances.

Note 2 – Some people may consider that putting HTML attributes into the form __init__ violates MVC principles, and it would be better to add these attributes in the template itself. I have done so in the past, using qTip2, however that requires more HTML and much more javascript, and I find this to be an overall neater solution. If you don’t initialise the popover, those additional attributes don’t do anything by themselves and only add a very small load to the browser. Django widgets are designed to allow modification of HTML, and it makes sense for me to take advantage of this capability because I use Bootstrap in tight coupling with my Django setup. I am very open to suggestions of people doing something similar in a different way, though – please comment :).