This website is made possible by displaying online advertisements to our visitors.
Please consider supporting us by disabling your ad blocker. Thank you for your support.
This website is made possible by displaying online advertisements to our visitors.
Please consider supporting us by disabling your ad blocker.

Django Forms with Django Crispy Forms

May 27 2021 Yacine Rouizi
Django Django Crispy Forms
Django Forms with Django Crispy Forms

Introduction

In this tutorial we are going to see how to integrate django-crispy-forms on your application and how to use it. django-crispy-forms allows you to manage your Django forms and makes rendering them easy. Also, it gives you a lot of freedom to affect the appearance and style of individual fields.

Project Configuration

If you want to follow along with me and you don't have a project just follow these steps:

mkdir django-crispy-forms
cd django-crispy-forms/
python3 -m venv venv
source venv/bin/activate
pip install django
django-admin startproject crispy .
django-admin startapp core
# install crispy forms
pip install django-crispy-forms
pip freeze > requirements.txt

Ok greet! Now that we have a working project, we need to make some configuration to start playing with django-crispy-forms.

Open up settings.py and add the following:

# crispy/settings.py
# ...
INSTALLED_APPS = [
    # ...
    'crispy_forms', # add this
]
# ...
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'], # Add this
        'APP_DIRS': True,
        'OPTIONS': {
            # ...
        },
    },
]

# ...
CRISPY_TEMPLATE_PACK = 'bootstrap4' # Add this

Don't forget to create the templates directory in the root directory of the project (at the same level as manage.py)

Setup Bootstrap

Now, we need a starter template to add Bootstrap, you can find it at getbootstrap.com. Here is what the base.html template will look like:

<!-- templates/base.html -->
<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
    <title>Hello, world!</title>
  </head>
  <body>
    <div class="container mt-5 pt-5 mb-5">
      {% block content %}
      {% endblock %}
    </div>

    <!-- You can remove these 3 lines because we are not going to use javascript-->
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.min.js" integrity="sha384-+YQ4JLhjyBLPDQt//I+STsc9iw4uQqACwlvpslubQzn4u2UU2UFM80nGisd026JF" crossorigin="anonymous"></script>
  </body>
</html>

Rendering Forms without Crispy

Let's now create a basic form and see how the Django rendering API render it. Create a forms.py file inside the core app and add the following code:

# core/forms.py
from django import forms

class BasicForm(forms.Form):
    GENDERS = (
        ('M', 'Male'),
        ('F', 'Female')
    )
    first_name = forms.CharField()
    last_name = forms.CharField()
    email = forms.EmailField()
    password = forms.CharField(widget=forms.PasswordInput())
    confirm_password = forms.CharField(widget=forms.PasswordInput())
    gender = forms.ChoiceField(widget=forms.RadioSelect, choices=GENDERS)
    # We should use a validator to make sure 
    # the user enters a valid number format
    phone_number = forms.CharField(label='Phone')
    about_you = forms.CharField(widget=forms.Textarea())

This is a regular form which contains different fields to see how they are rendered. Let's add a minimalist view to render this form:

# core/views.py
from django.shortcuts import render

from .forms import BasicForm

def signup(request):
    if request.method == 'POST':
        form = BasicForm(request.POST)
        if form.is_valid():
            pass
            
    else: 
        form = BasicForm()
    return render(request, 'signup.html', {'form': form})

urls.py

# crispy/urls.py
from django.contrib import admin
from django.urls import path

from core import views # Add this

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.signup, name='signup') # Add this
]

signup.html

<!-- templates/signup.html -->
{% extends 'base.html' %}

{% block content %}
    <form method="post" novalidate>
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Sign up</button>
    </form>
{% endblock %}

Here you can see the ugly form:

Without Crispy

Rendering Forms with Crispy

We will use the exact same code as above for the view, I just used a different name for the view and the template so that we can compare them:

views.py

# core/views.py
# ...
def crispy_signup(request):
    if request.method == 'POST':
        form = BasicForm(request.POST)
        if form.is_valid():
            pass 
    else: 
        form = BasicForm()
    return render(request, 'crispy_signup.html', {'form': form})

 urls.py

# crispy/urls.py
# ...
urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.signup, name='signup'),
    path('crispy/', views.crispy_signup, name='crispy_signup'), # Add this
]

crispy_signup.html

<!-- templates/crispy_signup.html -->
{% extends 'base.html' %}
{% load crispy_forms_tags %}

{% block content %}
    <form method="post" novalidate>
    {% csrf_token %}
    {{ form|crispy }}
    <button type="submit" class="btn btn-primary">Sign up</button>
    </form>
{% endblock %}

Note that now we use the crispy filter instead of as_p in the template

The form looks better:

With Crispy

Customizing the Fields with Crispy Forms

To have more control over the rendering of the form we can use the filter as_crispy_field, this way we can render each field individually.

Again we will use the same view as above except with a different name:

views.py

# core/views.py
# ...
def customized_crispy_signup(request):
    if request.method == 'POST':
        form = BasicForm(request.POST)
        if form.is_valid():
            pass 
    else: 
        form = BasicForm()
    return render(request, 'customized_crispy_signup.html', {'form': form})

urls.py

# crispy/urls.py
# ...
urlpatterns = [
    # ...
    path('customized_crispy/', views.customized_crispy_signup, name='customized_crispy_signup'),
]

 customized_crispy_signup.html

<!-- templates/customized_crispy_signup.html -->
{% extends 'base.html' %}
{% load crispy_forms_tags %}

{% block content %}
    <form method="post" novalidate>
    {% csrf_token %}
    <div class="form-row">
      <div class="form-group col-4">
          {{ form.first_name|as_crispy_field }}
      </div>
      <div class="form-group col-4">
        {{ form.last_name|as_crispy_field }}
      </div>
      <div class="form-group col-4">
        {{ form.email|as_crispy_field }}
      </div>
    </div>
    <div class="form-row">
      <div class="form-group col-6">
        {{ form.password|as_crispy_field }}
      </div>
      <div class="form-group col-6">
        {{ form.confirm_password|as_crispy_field }}
      </div>
    </div>
    <div class="form-row">
      <div class="form-group col-4">
        {{ form.gender|as_crispy_field }}
      </div>
      <div class="form-group col-8">
        {{ form.phone_number|as_crispy_field }}
      </div>
    </div>
      {{ form.about_you|as_crispy_field }}
    <button type="submit" class="btn btn-primary mt-4">Sign up</button>
  </form>
{% endblock %}

Here is the result:

Customized Crispy

The FormHelper and Layout Classes of Crispy

django-crispy-forms has two classes, FormHelper and Layout, that allow you to control the form rendering inside the forms.py and views.py files. For example, we can wrap the fields in divs, add html, set classes and ids and so on.

Let's try to use the Layout class to implement the form that we implemented before, open the forms.py file:

# core/forms.py
from django import forms
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Submit, Div

class BasicForm(forms.Form):
    # ...
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.layout = Layout(
            Div(
                Div('first_name', css_class='form-group col-4'),
                Div('last_name', css_class='form-group col-4'),
                Div('email', css_class='form-group col-4'),
                css_class='form-row'
            ),
            Div(
                Div('password', css_class='form-group col-4'),
                Div('confirm_password', css_class='form-group col-4'),
                css_class='form-row'
            ),
            Div(
                Div('gender', css_class='form-group col-4'),
                Div('phone_number', css_class='form-group col-8'),
                css_class='form-row'
            ),
            'about_you',
            Submit('submit', 'Sign up', css_class='mt-4')
        )

 views.py

# core/views.py
# ...
def crispy_helpers_signup(request):
    if request.method == 'POST':
        form = BasicForm(request.POST)
        if form.is_valid():
            pass 
    else: 
        form = BasicForm()
    return render(request, 'crispy_helpers_signup.html', {'form': form})

urls.py

# crispy/urls.py
from django.contrib import admin
from django.urls import path

from core import views

urlpatterns = [
    # ...
    path('crispy_helpers/', views.crispy_helpers_signup, name='crispy_helpers_signup'), # Add this
]

crispy_helpers_signup.html

<!-- templates/crispy_helpers_signup.html -->
{% extends 'base.html' %}

{% load crispy_forms_tags %}

{% block content %}
  {% crispy form %}
{% endblock %}

the rendered HTML is the same:

FormHelper and Layout

Sponsored

Summary

django-crispy-forms can save you a lot of time and in this tutorial we have only covered the basics. I hope that I gave you an idea on how to use the package with your application, also make sure to check the official documentation if you want to learn more.

You can find the code used in this tutorial on GitHub at: https://github.com/Rouizi/django-crispy-forms-tutorial

Related10 Best Python Books for Beginners and Advanced Programmers

Comments 4
Avatar Bernard Farrell said
Thanks for the useful summary on CrispyForms and especially the comparison of layout with and without Layout classes.

Nov. 22, 2021, 8:20 p.m.

Avatar Yacine said
Bernard, I am glad you find my content useful. Thank you for the positive feedback.
Also, thank you for buying me a coffee ;) You are awesome.

Nov. 22, 2021, 9:18 p.m.

Avatar Robson Ribeiro said
Gratidão. Era o que estava procurando para destravar meus conhecimentos no uso de formulários com o DJango.

March 27, 2022, 2:30 p.m.

Avatar Yacine said
@Robson I am happy you found the tutorial useful :) thank you.

March 27, 2022, 4:06 p.m.

Leave a comment

(Your email address will not be published)