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)
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.
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.
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
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' }}
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.
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.
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.
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
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)
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.
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.
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
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' }}
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.
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.
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.
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