Add class to form field Django ModelForm
Asked Answered
B

18

73

I am trying to write a Bootstrap Form with Django ModelForm. I have read the Django Documentation Django Documentation about Forms, so I have this code:

<div class="form-group">
{{ form.subject.errors }}
<label for="{{ form.subject.id_for_label }}">Email subject:</label>
{{ form.subject }}</div>

The {{form.subject}} is rendered by Django, for example in CharField field model, as input tag,

<input type="text"....> etc.

I need add "form-control" class to every input in order to get Bootstrap input appearance (without third-party packages). I found this solution Django add class to form <input ..> field. Is there any way to add a class to every field by default without specifying it in every attribute of the class of Form class?

class ExampleForm(forms.Form):
   name = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))
   email = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))
   address = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))
   country = forms.CharField(widget=forms.TextInput(attrs={'class':'form-control'}))

and so on ..

Brucebrucellosis answered 18/4, 2015 at 10:29 Comment(0)
D
130

If you can't use a third-party app and want to add a class (e.g., "form-control") to every field in a form in a DRY manner, you can do so in the form class __init__() method like so:

class ExampleForm(forms.Form):
    # Your declared form fields here
    ...

    def __init__(self, *args, **kwargs):
        super(ExampleForm, self).__init__(*args, **kwargs)
        for visible in self.visible_fields():
            visible.field.widget.attrs['class'] = 'form-control'

You might need to handle checking for existing classes in attrs too, if for some reason you'll be adding classes both declaratively and within __init__(). The above code doesn't account for that case.

Worth mentioning:

You specified that you don't want to use third-party packages. However, I'll take one second to mention that one of the simplest ways of automatically making forms render in the style of Bootstrap is to use django-crispy-forms, like this:

# settings.py
CRISPY_TEMPLATE_PACK = 'bootstrap3'

# forms.py
from crispy_forms.helper import FormHelper
class ExampleForm(forms.Form):
    # Your declared form fields here
    ...
    helper = FormHelper()

# In your template, this renders the form Bootstrap-style:
{% load crispy_forms_tags %}
{% crispy form %}
Doane answered 18/4, 2015 at 12:25 Comment(7)
Thanks everyone, i will try the def __init__() solution i hope it works.Isoleucine
After trying the solution, don't forget to mark one of the answers as "accepted", so that others searching for the same information can more easily find the answer.Doane
I know this is old. I tried the first method and got the error - 'NoneType' object has no attribute 'visible_fields'.Cirillo
Crispy forms are great. But when wanting to put 2 cols on 1 row, I had to open up the form in {{field}} and thats where I need to add a class 'form-control' manuallyIlliterate
@Illiterate I’m trying to do the same, for a workaround I ended up adding the classes using JavaScriptSkirl
If you use a lot of forms, a option for not have to override init every single time may be to create your own form class: class MyBaseForm(forms.Form): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) for visible in self.visible_fields(): visible.field.widget.attrs['class'] = 'form-control'. This way you can inherit from this class and it is going to automatically make the styles for you.Allemande
Regarding adding checking for existing classes. A small change to OP's code can achieve that. By changingvisible.field.widget.attrs['class'] = 'form-control' to: visible.field.widget.attrs['class'] = visible.field.widget.attrs.get('class', "") + ' form-control'Auroora
P
47

you can add CSS classes in forms.py

subject = forms.CharField(label='subject', 
                          max_length=100,
                          widget=forms.TextInput(
                              attrs={'class': "form-control"}))
Pomelo answered 25/5, 2018 at 7:51 Comment(3)
I wonder if it is possible to add classes in dict notation. I tried attrs=dict(class='my_class') but this obviously did not work, since Django (i.e. Python) was expecting a class declaration after classSaxhorn
maybe you are getting issue with class keyword ( as this is reserved ) use my syntax ( attrs={'class': "my_class"} )Pomelo
How should the widget look like for a ForeignKey and ImageField?Alleviate
B
23

Since it took me more hours, than I would like to (django newbie), to figure this out, I will place my outcome here aswell.

Setting widget to each field just to add one class over and over again is against programming rule of repeating and leads to many unneccessary rows. This especially happens when working with bootstrap forms.

Here is my (working) example for adding not only bootstrap classes:

forms.py

class CompanyForm(forms.Form):
    name = forms.CharField(label='Jméno')
    shortcut = forms.CharField(label='Zkratka')
    webpage = forms.URLField(label='Webové stránky')
    logo = forms.FileField(label='Logo')

templatetags/custom_tags.py

from django import template
from django.urls import reverse

register = template.Library()

@register.filter('input_type')
def input_type(ob):
    '''
    Extract form field type
    :param ob: form field
    :return: string of form field widget type
    '''
    return ob.field.widget.__class__.__name__


@register.filter(name='add_classes')
def add_classes(value, arg):
    '''
    Add provided classes to form field
    :param value: form field
    :param arg: string of classes seperated by ' '
    :return: edited field
    '''
    css_classes = value.field.widget.attrs.get('class', '')
    # check if class is set or empty and split its content to list (or init list)
    if css_classes:
        css_classes = css_classes.split(' ')
    else:
        css_classes = []
    # prepare new classes to list
    args = arg.split(' ')
    for a in args:
        if a not in css_classes:
            css_classes.append(a)
    # join back to single string
    return value.as_widget(attrs={'class': ' '.join(css_classes)})

reusable_form_fields.html (template)

{% load custom_tags %}

{% csrf_token %}
{% for field in form %}
    <div class="form-group row">
        {% if field|input_type == 'TextInput' %}
            <div for="{{ field.label }}" class="col-sm-2 col-form-label">
                {{ field.label_tag }}
            </div>
            <div class="col-sm-10">
                {{ field|add_classes:'form-control'}}
                {% if field.help_text %}
                    <small class="form-text text-muted">{{ field.help_text }}</small>
                {% endif %}
            </div>
        {% else %}
            ...
        {% endif %}
    </div>
{% endfor %}
Babyblueeyes answered 17/2, 2020 at 17:24 Comment(1)
This still works in 2024. However, I'd replace the last line in the add_classes() function with this: value.field.widget.attrs["class"] = ' '.join(css_classes) and then return value. This avoids converting it to string and will allow concatenation of multiple tags similar to this one.Parental
G
6

Crispy forms are the way to go . Tips for Bootstrap 4. Adding to @Christian Abbott's answer, For forms , bootstrap says, use form-group and form-control . This is how it worked for me .

My forms.py

class BlogPostForm(forms.ModelForm):
    class Meta:
        model = models.Post
        fields = ['title', 'text', 'tags', 'author', 'slug']
    helper = FormHelper()
    helper.form_class = 'form-group'
    helper.layout = Layout(
        Field('title', css_class='form-control mt-2 mb-3'),
        Field('text', rows="3", css_class='form-control mb-3'),
        Field('author', css_class='form-control mb-3'),
        Field('tags', css_class='form-control mb-3'),
        Field('slug', css_class='form-control'),
    )

My post_create.html

{% extends 'blog/new_blog_base.html' %}
{% load crispy_forms_tags %}
{% block content %}
<div class="container">

<form method='POST' enctype="multipart/form-data">
    {% csrf_token %}
    {{ form.media }}
    {% crispy form %}

<hr>
<input type="submit" name="Save" value="Save" class='btn btn-primary'> <a href="{% url 'home' %}" class='btn btn-danger'>Cancel</a>
</form>

</div>
{% endblock %}

Note : If you are using CK Editor RichTextField() for your model field , then that field wont be affected . If anyone knows about it , do update this .

Gauhati answered 1/6, 2018 at 7:40 Comment(0)
W
4

You can also explicity mention the field that you want to apply the class to

class ProfileForm(ModelForm):

   class Meta:
       model = Profile 
        fields = ['avatar','company']  
    
    
        def __init__(self, *args, **kwargs):
           super().__init__(*args, **kwargs)
           self.fields['avatar'].widget.attrs.update({'class': 'form-control'})
           self.fields['company'].widget.attrs.update({'class':'form-control'})
Wolford answered 16/11, 2021 at 9:53 Comment(0)
S
4

I understood "no third-party libs", but this one django-widget-tweaks

really WORTH MENTIONING

is simple, DRY and powerfull. give you full control over the widget rendering doesnt matter which css framework you are using ... still simple

  • you manage many html attributes you want on HTML not Django forms.
  • User template "filters" not template tags (as a "normal" form var)
  • You control the input and labels

django-widget-tweaks

-> https://github.com/jazzband/django-widget-tweaks

Sample ...

{{form.hours|attr:"class:form-control form-control-sm"}}
Shofar answered 14/12, 2022 at 20:38 Comment(3)
not sure why I did recieve a negative point, the question is "Add class to form field Django ModelForm" well, the lib pointed in my answer do exactly that in a dry (pratical) way.Shofar
is there a way using that library to add a class to all form fields at once?Cornfield
well, I would do that by rewriting the templates field . @EbramShehata. Or you may customize the lib (My last option). Other option and much more flexible , use the DjangoTweaks and add the class for every field... it doent has to be manually, at least a regex + replace (code editor) do the jobShofar
W
2

This is very practical:

class CreateSomethingForm(forms.ModelForm):

    class Meta:
        model = Something
        exclude = []

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields.values():
            field.widget.attrs['class'] = 'form-control'

In this way you don't have to go field by field.

Wapiti answered 11/1, 2022 at 15:21 Comment(0)
F
2

You can add classes in your forms.py inside the Meta class:

class Form(forms.ModelForm):
    class Meta:
        model = ModelForm
        fields = "__all__"
        widgets = {
            'name': forms.TextInput(attrs={'class':'form-control'})
        }
Funches answered 1/12, 2022 at 3:40 Comment(0)
R
1

I found it easier to identify the element via css and add the styling there. With django forms you get a unique id for each form field (user form prefixes if you display the form multiple times in your template).

# views.py
def my_view_function(request):
    form_a = MyForm(prefix="a")
    form_b = MyForm(prefix="b")
    context = {
        "form_a": form_a,
        "form_b": form_b
    }
    return render(request, "template/file.html", context)

style

// file.css
form input#by_id {
  width: 100%;
}
Rundgren answered 20/2, 2020 at 0:3 Comment(2)
Thank you soooo much. I've been researching how to change the input field for the longest time in the best manner for my use case and i had form#id_term and your simple addition of input has bought me such relief. Thank you. Could you share where you saw that css classes for form fields are to be set with an input label after it? Are there any alternatives to form input? Form label#id_term? Form div#id_term?Abbe
Its been a while since I worked on this. I just remembered seeing form prefixes and I noticed they are given a css id. Search for "prefix" on this page docs.djangoproject.com/en/4.0/ref/forms/api/…Rundgren
A
1

This is a answer complemeting @Christian Abbott correct answer.

If you use a lot of forms, a option for not having to override init every single time may be to create your own form class:

class MyBaseForm(forms.Form):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for visible in self.visible_fields():
            visible.field.widget.attrs['class'] = 'form-control'

Then you can inherit from this class and it is going to automatically make the styles for you.

class ExampleForm(MyBaseForm):
    # Your declared form fields here
    ...

Same thing can be done with ModelForm by simply creating a MyBaseModelForm that inherits from ModelForm.

Allemande answered 27/10, 2021 at 22:37 Comment(0)
Y
0

One way is to create base form class and manually update the field's attribute inside __init__ method.

Another is by using already existing libraries like this one: https://github.com/dyve/django-bootstrap3

There are plenty of these libraries around github. Look around.

Yahoo answered 18/4, 2015 at 11:56 Comment(0)
F
0

Ok some time has passed but i had the same issues. I came to this solution:

class FormCssAttrsMixin():
    cssAttrs = {}

    def inject_css_attrs(self):
        # iterate through fields
        for field in self.fields:
            widget = self.fields[field].widget
            widgetClassName = widget.__class__.__name__

            # found widget which should be manipulated?
            if widgetClassName in self.cssAttrs.keys():
                # inject attributes
                attrs = self.cssAttrs[widgetClassName]
                for attr in attrs:
                    if attr in widget.attrs:  # attribute already existing
                        widget.attrs.update[attr] = widget[attr] + " " +    attrs[attr]  # append
                    else:  # create attribute since its not existing yet
                        widget.attrs[attr] = attrs[attr]


class MyForm(FormCssAttrsMixin, forms.Form):
    # add class attribute to all django textinputs widgets
    cssAttrs = {"TextInput": {"class": "form-control"}}

    name = forms.CharField()
    email = forms.CharField()
    address = forms.CharField()
    country = forms.CharField()

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)

        self.inject_css_attrs()

With this Mixin class you can manipulate the attributes of form widgets in a generic way. Simply add a dictionary as class variable which contains the desired attributes and values per widget. This way you can add your css classes at the same location where you define your fields. Only downside is, that you have to call the "inject_css_attrs" method somewhere but i think that is ok.

Flaxman answered 17/7, 2021 at 9:32 Comment(0)
C
0

A generalized version of @christian-abbott response:

class ExampleForm(forms.Form):
    
    _HTML_CLASSES = ('form-control', 'something-else')
    
    # Your declared form fields here
    ...

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for visible in self.visible_fields():
            missing_classes = list(self._HTML_CLASSES)
            if 'class' in visible.field.widget.attrs:
                current_classes = visible.field.widget.attrs['class'].split(' ')
                for current_class in current_classes:
                    if current_class in missing_classes:
                        missing_classes.remove(current_class)
            else:
                current_classes = []
            visible.field.widget.attrs['class'] = ' '.join(current_classes + missing_classes)
Cobby answered 18/2, 2022 at 5:31 Comment(0)
B
0

If you just need to change the class for bootstrap purposes, you can just add a script to the template.

<script>
   const elementsInputs = document.querySelectorAll('input[id^="id_"]');
   elementsInputs.forEach(element => {
       element.classList.add("form-control");
   });
   const elementsLabels = document.querySelectorAll('label[for^="id_"]');
   elementsLabels.forEach(element => {
       element.classList.add("form-label");
   });
</script>

then the form fields in the template should be something like:

<div class="fieldWrapper">
    {{ form.subject.errors }}
    {{ form.subject.label_tag }}
    {{ form.subject }}
</div>

as described in Django.

Bellona answered 30/7, 2022 at 23:49 Comment(0)
L
0

You can do it without any external libraries or code changes, right in the template. Like this:

{% for field in form %}
<div class="input_item">
    <p class="title">{{ field.label }}:</p>
    <div class="form-group">
        <{{ field|cut:"<"|cut:">" }} class="form-control">
    </div>
</div>
{% endfor %}

However, it is not the best solution. If you can create templatetag - go for it.

Larrikin answered 22/1, 2023 at 20:25 Comment(0)
C
0

Like this:

title.widget.attrs['class'] = 'form-control'

Just change title to the name of your input, and form-control to whatever class you want to add to this input field.

Coeternal answered 24/8, 2023 at 9:32 Comment(1)
There are 17 existing answers to this question, including a top-voted, accepted answer with over one hundred votes. Are you certain your solution hasn't already been given? If not, why do you believe your approach improves upon the existing proposals, which have been validated by the community? Offering an explanation is always useful on Stack Overflow, but it's especially important where the question has been resolved to the satisfaction of both the OP and the community. Help readers out by explaining what your answer does different and when it might be preferred.Murther
V
-2

you can use row-cols-5

<div class="row row-cols-5">
  <div class="col">1</div>
  <div class="col">2</div>
  <div class="col">3</div>
  <div class="col">4</div>
  <div class="col">5</div>
</div>
Vulgarize answered 3/10, 2022 at 11:34 Comment(0)
O
-3

I know that author asked about Bootstrap for own Form, but there is an additional way to include Bootstrap class tag in Django form for authentication, password reset etc.

If we create template with standard form:

<form action="" method="post">
   {% csrf_token %}
   {{ form }}
</form>

then in browser source code we can see all the form fields with the tags:

<form action="" method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="xxx">
    <tr><th><label for="id_old_password">Old password:</label></th><td><input type="password" name="old_password" autofocus required id="id_old_password"></td></tr>
    <tr><th><label for="id_new_password1">New password:</label></th><td><input type="password" name="new_password1" required id="id_new_password1"></td></tr>
    <tr><th><label for="id_new_password2">New password confirmation:</label></th><td><input type="password" name="new_password2" required id="id_new_password2"></td></tr>
</form>

Variable {{ form }} in our template now can be replaced with this code and Bootstrap classes we needed:

<div class="fieldWrapper form-group" aria-required="true">
    <label for="id_old_password">Old password:</label><span class="required">*</span>
    <input type="password" **class="form-control"** name="old_password" autofocus required id="id_old_password">
</div>

Maybe it could be useful for redesign built-in static forms.

Ory answered 27/9, 2019 at 19:14 Comment(1)
This is very wrong, you will loose 1) server validation feedback, 2) hints, 3) changes to the model 4) possibly security 5) it's not flexible 6) its not maintainable 7) etc...Farlay

© 2022 - 2024 — McMap. All rights reserved.