183

I would like to style the following:

forms.py:

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    email = forms.EmailField(required=False)
    message = forms.CharField(widget=forms.Textarea)

contact_form.html:

<form action="" method="post">
  <table>
    {{ form.as_table }}
  </table>
  <input type="submit" value="Submit">
</form>

For example, how do I set a class or ID for the subject, email, message to provide an external style sheet to?

daaawx
  • 3,273
  • 2
  • 17
  • 16
David542
  • 104,438
  • 178
  • 489
  • 842

17 Answers17

246

Taken from my answer to: How to markup form fields with <div class='field_type'> in Django

class MyForm(forms.Form):
    myfield = forms.CharField(widget=forms.TextInput(attrs={'class': 'myfieldclass'}))

or

class MyForm(forms.ModelForm):
    class Meta:
        model = MyModel

    def __init__(self, *args, **kwargs):
        super(MyForm, self).__init__(*args, **kwargs)
        self.fields['myfield'].widget.attrs.update({'class': 'myfieldclass'})

or

class MyForm(forms.ModelForm):
    class Meta:
        model = MyModel
        widgets = {
            'myfield': forms.TextInput(attrs={'class': 'myfieldclass'}),
        }

--- EDIT ---
The above is the easiest change to make to original question's code that accomplishes what was asked. It also keeps you from repeating yourself if you reuse the form in other places; your classes or other attributes just work if you use the Django's as_table/as_ul/as_p form methods. If you need full control for a completely custom rendering, this is clearly documented

-- EDIT 2 ---
Added a newer way to specify widget and attrs for a ModelForm.

shadfc
  • 6,104
  • 3
  • 25
  • 19
  • 35
    Though it is not recommended to mix presentation with business logic. – Torsten Engelbrecht Apr 29 '11 at 04:08
  • 9
    How is this presentation? You're giving the element a class, which is just an identifier or categorization. You still have to define what that does elsewhere – shadfc Apr 29 '11 at 13:17
  • 9
    Yes and no. First CSS classes are by convention used for styling, if you need an unique identifier its better to use `id`. Second its usually the template side's responsobilty to do exaclty this, Esspecially if you are going to access this class via frontend methods (js, css). I didn't say your answer is wrong. In my opinion its just bad practice (esspecially when your working in a team with frontend and backend developers). – Torsten Engelbrecht Apr 30 '11 at 03:12
  • 9
    This looks ridiculous, just to add a class you need this much code? It seems it'd be easier just to hard-code the HTML/CSS in these areas (especially for a CSS-heavy site). – David542 Apr 30 '11 at 05:25
  • 17
    It is insane django makes this so awkward! – Bryce Jun 17 '12 at 05:37
  • 6
    That is NOT a good way to do it, it forces you to put presentation stuff in python code (assuming than CSS classes are often used for css purposes). A custom filter is a cleaner and easier solution, for sure. Watch the Charlesthk answer. – David Dahan May 27 '14 at 15:16
  • @shadfc the link on [link](https://docs.djangoproject.com/en/1.3/topics/forms/#customizing-the-form-template) (clearly documented) returns 404. – unlockme Jul 08 '16 at 12:53
  • @user1743931 Fixed – shadfc Jul 08 '16 at 13:10
  • 1
    Final example was precisely what I needed. SOoooo useful. I'm happy to leave aside the arguments over whether this is the 'right' way to do it. – Martin CR Jun 29 '19 at 06:19
  • The more I use Django the more I'm convinced the entire framework is an antipattern. – Our_Benefactors Nov 04 '19 at 22:06
  • @Our_Benefactors this means two things: you don't really know the framework or you just don't know design patters. This answer can be considered a bad-practice from a design perspective, django offer a very clean solution with filters: look at Charlesthk answer. – lorenzo Dec 19 '19 at 08:52
  • @lorenzo_campanile We have different definitions of "very clean". To me, this is cumbersome and not at all clean compared to most frameworks because they offer this functionality out of the box, which Django doesn't. That Django isn't built with this feature in mind doesn't speak well to the robustness of the framework. – Our_Benefactors Dec 19 '19 at 16:10
  • I do not agree but as you said we've different points of view. With Django you can auto-generate the form with "{{ form }}" or write each field manually, I find Django Forms very comfortable. At this point I'm just curious to know... what is an example of a robust Web Framework for you? Spring? Laravel? Flask? – lorenzo Dec 20 '19 at 13:06
  • How could I use this for a CreateView? Could I overwrite get_form? – MareksNo Nov 03 '20 at 07:01
  • A cleaner way to do this is to build your own widget, inheriting from Django ones, and then overriding the `template_name` value. Then, you just need to create a template file containing your markup and styling. – David Dahan May 10 '22 at 13:07
  • An even cleaner way is to use django-widget-tweaks package. – David Dahan May 10 '22 at 14:36
140

This can be done using a custom template filter. Consider rendering your form this way:

<form action="/contact/" method="post">
  {{ form.non_field_errors }}
  <div class="fieldWrapper">
    {{ form.subject.errors }}
    {{ form.subject.label_tag }}
    {{ form.subject }}
    <span class="helptext">{{ form.subject.help_text }}</span>
  </div>
</form>

form.subject is an instance of BoundField which has the as_widget() method.

You can create a custom filter addclass in my_app/templatetags/myfilters.py:

from django import template

register = template.Library()

@register.filter(name='addclass')
def addclass(value, arg):
    return value.as_widget(attrs={'class': arg})

And then apply your filter:

{% load myfilters %}

<form action="/contact/" method="post">
  {{ form.non_field_errors }}
  <div class="fieldWrapper">
    {{ form.subject.errors }}
    {{ form.subject.label_tag }}
    {{ form.subject|addclass:'MyClass' }}
    <span class="helptext">{{ form.subject.help_text }}</span>
  </div>
</form>

form.subjects will then be rendered with the MyClass CSS class.

daaawx
  • 3,273
  • 2
  • 17
  • 16
Charlesthk
  • 9,394
  • 5
  • 43
  • 45
  • 9
    This is one of the cleaner & easy to implement solutions – user Feb 06 '14 at 18:54
  • 6
    This answer should be the top answer!!! It's really cleaner than the Django proposed solution! Well done @Charlesthk – David Dahan May 27 '14 at 13:02
  • 6
    Super helpful. It wasn't obvious to me at first, but you can use this to add multiple classes too: `{{ form.subject|addclass:'myclass1 myclass2' }}` – smg Dec 17 '16 at 00:45
  • I like that this allows keeping the HTML classes in the HTML files. When working with styling, I jump back and forth between stylesheets and structure, not models and/or forms. – Kevin Jul 26 '17 at 16:41
  • 1
    one issue with this method is that this filter converts the `BoundField` into `SafeString`, so other (similar) filters cannot be chained. `django-widget-tweaks` returns fields so it's a more robust solution. – minusf Sep 24 '20 at 09:09
  • I can confirm that this approach works with Jinja2, using Django 3.2 (without need for the register decorator). – xax Oct 14 '21 at 09:38
34

If you don't want to add any code to the form (as mentioned in the comments to @shadfc's Answer), it is certainly possible, here are two options.

First, you just reference the fields individually in the HTML, rather than the entire form at once:

<form action="" method="post">
    <ul class="contactList">
        <li id="subject" class="contact">{{ form.subject }}</li>
        <li id="email" class="contact">{{ form.email }}</li>
        <li id="message" class="contact">{{ form.message }}</li>
    </ul>
    <input type="submit" value="Submit">
</form>

(Note that I also changed it to a unsorted list.)

Second, note in the docs on outputting forms as HTML, Django:

The Field id, is generated by prepending 'id_' to the Field name. The id attributes and tags are included in the output by default.

All of your form fields already have a unique id. So you would reference id_subject in your CSS file to style the subject field. I should note, this is how the form behaves when you take the default HTML, which requires just printing the form, not the individual fields:

<ul class="contactList">
    {{ form }}  # Will auto-generate HTML with id_subject, id_email, email_message 
    {{ form.as_ul }} # might also work, haven't tested
</ul>

See the previous link for other options when outputting forms (you can do tables, etc).

Note - I realize this isn't the same as adding a class to each element (if you added a field to the Form, you'd need to update the CSS also) - but it's easy enough to reference all of the fields by id in your CSS like this:

#id_subject, #id_email, #email_message 
{color: red;}
John C
  • 6,285
  • 12
  • 45
  • 69
  • I tried your second solution but it did not work. I created class for the id_email and it failed to produce any results. – almost a beginner Dec 19 '16 at 09:51
  • @almostabeginner one thing I can suggest for debugging - once you see the page in a browser, use _View Page Source_ (generally by right-clicking), and look at the actual full page that Django is generating. See if the fields exist, with the _id_ or _class_ identifier that you expect. Also most browsers (possibly by installing a plugin) can run a debugger that shows you the _css_ which is applied to a page, also helpful to see what's going on. – John C Dec 19 '16 at 18:08
  • @almostabeginner also note, I added a bit of sample code. In case it wasn't clear from the text alone - you have to reference the form itself, not individual fields, at which point the form auto-generates HTML that contains _ids_, as described. Hopefully that helps. – John C Dec 20 '16 at 14:50
  • 1
    Thanks for the help, the issue wasn't my css at all, the issue was related to cache. So my old css was stored therefore none of the changes would display. I just cleared the cache from chrome and all the updates started showing. – almost a beginner Dec 28 '16 at 02:22
27

Per this blog post, you can add css classes to your fields using a custom template filter.

from django import template
register = template.Library()

@register.filter(name='addcss')
def addcss(field, css):
    return field.as_widget(attrs={"class":css})

Put this in your app's templatetags/ folder and you can now do

{{field|addcss:"form-control"}}
aashanand
  • 714
  • 1
  • 6
  • 15
14

You can do like this:

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    subject.widget.attrs.update({'id' : 'your_id'})

Hope that works.

Ignas

Ignas Butėnas
  • 6,061
  • 5
  • 32
  • 47
10

You could use this library: https://pypi.python.org/pypi/django-widget-tweaks

It allows you to do the following:

{% load widget_tweaks %}
<!-- add 2 extra css classes to field element -->
{{ form.title|add_class:"css_class_1 css_class_2" }}
Eamonn Faherty
  • 137
  • 1
  • 7
  • 1
    Take a look at the Charlesthk solution, it's the same without adding an extra library :) – David Dahan May 27 '14 at 13:03
  • @DavidW.: Yes, but Widget Tweaks has lots of more filters, such as `render_field`. – mrdaliri Oct 19 '14 at 05:09
  • Charlesthk solution converts the `BoundField` into `SafeString`, so other (similar) filters cannot be chained. `django-widget-tweaks` returns fields so it's a more robust solution. – minusf Sep 24 '20 at 09:10
7

Write your form like:

    class MyForm(forms.Form):
         name = forms.CharField(widget=forms.TextInput(attr={'class':'name'}),label="Your Name")
         message = forms.CharField(widget=forms.Textarea(attr={'class':'message'}), label="Your Message")

In your HTML field do something like:

{% for field in form %}
      <div class="row">
        <label for="{{ field.name}}">{{ field.label}}</label>{{ field }}
     </div>
{% endfor %}

Then in your CSS write something like:

.name{
      /* you already have this class so create it's style form here */
}
.message{
      /* you already have this class so create it's style form here */
}
label[for='message']{
      /* style for label */
}

Hope this answer is worth a try! Note you must have written your views to render the HTML file that contains the form.

msahin
  • 1,520
  • 1
  • 16
  • 22
Aula
  • 121
  • 1
  • 3
5

You can do:

<form action="" method="post">
    <table>
        {% for field in form %}
        <tr><td>{{field}}</td></tr>
        {% endfor %}
    </table>
    <input type="submit" value="Submit">
</form>

Then you can add classes/id's to for example the <td> tag. You can of course use any others tags you want. Check Working with Django forms as an example what is available for each field in the form ({{field}} for example is just outputting the input tag, not the label and so on).

Torsten Engelbrecht
  • 13,318
  • 4
  • 46
  • 48
3

Didn't see this one really...

https://docs.djangoproject.com/en/1.8/ref/forms/api/#more-granular-output

More granular output

The as_p(), as_ul() and as_table() methods are simply shortcuts for lazy developers – they’re not the only way a form object can be displayed.

class BoundField Used to display HTML or access attributes for a single field of a Form instance.

The str() (unicode on Python 2) method of this object displays the HTML for this field.

To retrieve a single BoundField, use dictionary lookup syntax on your form using the field’s name as the key:

>>> form = ContactForm()
>>> print(form['subject'])
<input id="id_subject" type="text" name="subject" maxlength="100" />

To retrieve all BoundField objects, iterate the form:

>>> form = ContactForm()
>>> for boundfield in form: print(boundfield)
<input id="id_subject" type="text" name="subject" maxlength="100" />
<input type="text" name="message" id="id_message" />
<input type="email" name="sender" id="id_sender" />
<input type="checkbox" name="cc_myself" id="id_cc_myself" />

The field-specific output honors the form object’s auto_id setting:

>>> f = ContactForm(auto_id=False)
>>> print(f['message'])
<input type="text" name="message" />
>>> f = ContactForm(auto_id='id_%s')
>>> print(f['message'])
<input type="text" name="message" id="id_message" />
Community
  • 1
  • 1
BilliAm
  • 590
  • 2
  • 6
  • 26
3

One solution is to use JavaScript to add the required CSS classes after the page is ready. For example, styling django form output with bootstrap classes (jQuery used for brevity):

<script type="text/javascript">
    $(document).ready(function() {
        $('#some_django_form_id').find("input[type='text'], select, textarea").each(function(index, element) {
            $(element).addClass("form-control");
        });
    });
</script>

This avoids the ugliness of mixing styling specifics with your business logic.

Simon Feltman
  • 1,120
  • 7
  • 8
2

There is a very easy to install and great tool made for Django that I use for styling and it can be used for every frontend framework like Bootstrap, Materialize, Foundation, etc. It is called widget-tweaks Documentation: Widget Tweaks

  1. You can use it with Django's generic views
  2. Or with your own forms:

from django import forms

class ContactForm(forms.Form):
    subject = forms.CharField(max_length=100)
    email = forms.EmailField(required=False)
    message = forms.CharField(widget=forms.Textarea)

Instead of using default:

{{ form.as_p }} or {{ form.as_ul }}

You can edit it your own way using the render_field attribute that gives you a more html-like way of styling it like this example:

template.html

{% load widget_tweaks %}

<div class="container">
   <div class="col-md-4">
      {% render_field form.subject class+="form-control myCSSclass" placeholder="Enter your subject here" %}
   </div>
   <div class="col-md-4">
      {% render_field form.email type="email" class+="myCSSclassX myCSSclass2" %}
   </div>
   <div class="col-md-4">
      {% render_field form.message class+="myCSSclass" rows="4" cols="6" placeholder=form.message.label %}
   </div>
</div>

This library gives you the opportunity to have well separated yout front end from your backend

Alam Téllez
  • 178
  • 9
2

You may not need to override your form class' __init__, because Django sets name & id attributes in the HTML inputs. You can have CSS like this:

form input[name='subject'] {
    font-size: xx-large;
}
tehfink
  • 447
  • 8
  • 7
  • 1
    To add to this. Given "subject = forms...", id="id_subject" and name="subject" is the Django convention for these attributes. Therefore you should also be able to do #id_subject{ ... } – solartic Apr 29 '11 at 04:12
  • @solartic: You're right, thanks. I didn't mention this because the `id` field created by Django for formsets gets pretty hairy… – tehfink Apr 29 '11 at 19:14
1

In Django 1.10 (possibly earlier as well) you can do it as follows.

Model:

class Todo(models.Model):
    todo_name = models.CharField(max_length=200)
    todo_description = models.CharField(max_length=200, default="")
    todo_created = models.DateTimeField('date created')
    todo_completed = models.BooleanField(default=False)

    def __str__(self):
        return self.todo_name

Form:

class TodoUpdateForm(forms.ModelForm):
    class Meta:
        model = Todo
        exclude = ('todo_created','todo_completed')

Template:

<form action="" method="post">{% csrf_token %}
    {{ form.non_field_errors }}
<div class="fieldWrapper">
    {{ form.todo_name.errors }}
    <label for="{{ form.name.id_for_label }}">Name:</label>
    {{ form.todo_name }}
</div>
<div class="fieldWrapper">
    {{ form.todo_description.errors }}
    <label for="{{ form.todo_description.id_for_label }}">Description</label>
    {{ form.todo_description }}
</div>
    <input type="submit" value="Update" />
</form>
MadPhysicist
  • 5,401
  • 11
  • 42
  • 107
1

For larger form instead of writing css classed for every field you could to this

class UserRegistration(forms.ModelForm):
   # list charfields

   class Meta:
      model = User
      fields = ('username', 'first_name', 'last_name', 'email', 'password', 'password2')

   def __init__(self, *args, **kwargs):
      super(UserRegistration, self).__init__(*args, **kwargs)
      for field in self.fields:
         self.fields[field].widget.attrs['class'] = 'form-control'
Martian4x
  • 47
  • 12
0

Edit: Another (slightly better) way of doing what I'm suggesting is answered here: Django form input field styling

All the above options are awesome, just thought I'd throw in this one because it's different.

If you want custom styling, classes, etc. on your forms, you can make an html input in your template that matches your form field. For a CharField, for example, (default widget is TextInput), let's say you want a bootstrap-looking text input. You would do something like this:

<input type="text" class="form-control" name="form_field_name_here">

And as long as you put the form field name matches the html name attribue, (and the widget probably needs to match the input type as well) Django will run all the same validators on that field when you run validate or form.is_valid() and

Styling other things like labels, error messages, and help text don't require much workaround because you can do something like form.field.error.as_text and style them however you want. The actual fields are the ones that require some fiddling.

I don't know if this is the best way, or the way I would recommend, but it is a way, and it might be right for someone.

Here's a useful walkthrough of styling forms and it includes most of the answers listed on SO (like using the attr on the widgets and widget tweaks). https://simpleisbetterthancomplex.com/article/2017/08/19/how-to-render-django-form-manually.html

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
kimbo
  • 2,513
  • 1
  • 15
  • 24
0

Styling widget instances

If you want to make one widget instance look different from another, you will need to specify additional attributes at the time when the widget object is instantiated and assigned to a form field (and perhaps add some rules to your CSS files).

https://docs.djangoproject.com/en/2.2/ref/forms/widgets/

To do this, you use the Widget.attrs argument when creating the widget:

class CommentForm(forms.Form):
    name = forms.CharField(widget=forms.TextInput(attrs={'class': 'special'}))
    url = forms.URLField()
    comment = forms.CharField(widget=forms.TextInput(attrs={'size': '40'}))

You can also modify a widget in the form definition:

class CommentForm(forms.Form):
    name = forms.CharField()
    url = forms.URLField()
    comment = forms.CharField()

    name.widget.attrs.update({'class': 'special'})
    comment.widget.attrs.update(size='40')

Or if the field isn’t declared directly on the form (such as model form fields), you can use the Form.fields attribute:

class CommentForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['name'].widget.attrs.update({'class': 'special'})
        self.fields['comment'].widget.attrs.update(size='40')

Django will then include the extra attributes in the rendered output:

>>> f = CommentForm(auto_id=False)
>>> f.as_table()
<tr><th>Name:</th><td><input type="text" name="name" class="special" required></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" required></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" size="40" required></td></tr>
Freman Zhang
  • 491
  • 6
  • 6
0

I was playing around with this solution to maintain consistency throughout the app:

def bootstrap_django_fields(field_klass, css_class):
    class Wrapper(field_klass):
        def __init__(self, **kwargs):
            super().__init__(**kwargs)

        def widget_attrs(self, widget):
            attrs = super().widget_attrs(widget)
            if not widget.is_hidden:
                attrs["class"] = css_class
            return attrs

    return Wrapper


MyAppCharField = bootstrap_django_fields(forms.CharField, "form-control")

Then you don't have to define your css classes on a form by form basis, just use your custom form field.


It's also technically possible to redefine Django's forms classes on startup like so:

forms.CharField = bootstrap_django_fields(forms.CharField, "form-control")

Then you could set the styling globally even for apps not in your direct control. This seems pretty sketchy, so I am not sure if I can recommend this.

jsolum
  • 196
  • 2
  • 15