Django: Adding CSS classes when rendering form fields in a template

Question:

I’m outputting fields of a form in a template like this {{ form.first_name }} and I’d like to add a class (eg. blueprint’s span-x-class) to it. So I’d like to know if there’s a nice readymade solution (template filter) for that, which I could use in the fashion {{ form.first_name|add_class:"span-4" }}? (I just want to know if Django’s developers or anybody has thought of that yet without my knowledge before doing it on my own)

Asked By: Bernhard Vallant

||

Answers:

You need to specify the widget explicitly and add the class using the attrs keyword argument. There is no other way that I know of.

However, if this is too cumbersome, you could still just wrap the field in another element, such as div or span and add a class to that. Then modify your CSS accordingly.

Answered By: Deniz Dogan

I am still learning Django, but couldn’t you do this something like this –

from django import forms

class SomeForm(forms.Form):
    f = forms.CharField(label='x',widget=forms.TextInput(attrs={'class':'name'}))

I guess there is no need to do this at the template level (or use filters) unless you have some requirement that I did not understand.

Answered By: Srikar Appalaraju

To solve this I made my own template filter, you can apply it on any tag, not just input elements!

class_re = re.compile(r'(?<=class=["'])(.*)(?=["'])')
@register.filter
def add_class(value, css_class):
    string = unicode(value)
    match = class_re.search(string)
    if match:
        m = re.search(r'^%s$|^%ss|s%ss|s%s$' % (css_class, css_class, 
                                                    css_class, css_class), 
                                                    match.group(1))
        print match.group(1)
        if not m:
            return mark_safe(class_re.sub(match.group(1) + " " + css_class, 
                                          string))
    else:
        return mark_safe(string.replace('>', ' class="%s">' % css_class))
    return value
Answered By: Bernhard Vallant

A few extra notes on how to get going with Lazerscience’s very handy solution. Here’s how the file looks with dependency imports:

import re
from django.utils.safestring import mark_safe
from django import template
register = template.Library()

class_re = re.compile(r'(?<=class=["'])(.*)(?=["'])')
@register.filter
def add_class(value, css_class):
    string = unicode(value)
    match = class_re.search(string)
    if match:
        m = re.search(r'^%s$|^%ss|s%ss|s%s$' % (css_class, css_class, 
                                                    css_class, css_class), 
                                                    match.group(1))
        print match.group(1)
        if not m:
            return mark_safe(class_re.sub(match.group(1) + " " + css_class, 
                                          string))
    else:
        return mark_safe(string.replace('>', ' class="%s">' % css_class))
    return value

I pasted this into a file called add_class.py. The directory structure is:
mydjangoproject > general_tools_app > templatetags > add_class.py

general_tools_app is an application which collects useful functionality like this that I add to new django projects.

(The general_tools_app and templatetags directories both have an empty __init__.py file so that they get registered correctly)

In settings.py, my INSTALLED_APPS tuple includes the entry ‘mydjangoproject.general_tools_app’.

To use the filter in a template, I add the line {% load add_class %} at the top of the file. If I want to add the class ‘delete’ to a field, I’d do this

{{ myfield|add_class:'delete' }}
Answered By: bitbutter

One more note on lazerscience’s solution: if you apply this to a <select> element with no class attribute, the string replace in the else case would produce something like this:

<select name="state" id="id_state" class="class1 class2">
    <option value="AL" class="class1 class2">Alabama</option class="class1 class2">
    <option value="AK" class="class1 class2">Alaska</option class="class1 class2">
    <option value="AZ" class="class1 class2">Arizona</option class="class1 class2">
</select class="class1 class2">

I’m pretty sure browsers scrap those extraneous class definitions, but that’s a lot of string replaces when you only need one. An easy remedy for this:

return mark_safe(string.replace('>', ' class="%s">' % css_class, 1))

That last argument (1) will ensure that only the first instance of “>” will be replaced with “class=”…”>, which is logically correct for any form element.

Answered By: Gavin

You only need to install Django widget_tweaks

pip install django-widget-tweaks

After you can to do something like that on your template:

{{ form.search_query|attr:"type:search" }}

Read all about it here.

Answered By: Cristian Rojas

Another way is to use the as_widget method on a field to alter it — simpler and safer than the regex approach, and doesn’t require any additional dependencies.

Define a custom template filter:

@register.filter
def add_class(field, class_name):
    return field.as_widget(attrs={
        "class": " ".join((field.css_classes(), class_name))
    })

And in your template:

{{ form.first_name|add_class:"span-4" }}

You can also use as_widget to add other attributes, like placeholder, etc.

Answered By: user85461

Updated for python3

It was simple to update, just needed parenthesis on the print and a change of unicode(value) to str(value), I also just quickly changed the ‘ class=”%s”>’ to an fstring.

import re
from django.utils.safestring import mark_safe
from django import template
register = template.Library()

class_re = re.compile(r'(?<=class=["'])(.*)(?=["'])')
@register.filter
def add_class(value, css_class):
    string = str(value)
    match = class_re.search(string)
    if match:
        m = re.search(r'^%s$|^%ss|s%ss|s%s$' % (css_class, css_class, 
                                                    css_class, css_class), 
                                                    match.group(1))
        print (match.group(1))
        if not m:
            return mark_safe(class_re.sub(match.group(1) + " " + css_class, 
                                          string))
    else:
        return mark_safe(string.replace('>', f' class="{css_class}">'))
    return value
Answered By: dotslashshawn
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.