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.
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)
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>
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:
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:
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:
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:
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
Related: 10 Best Python Books for Beginners and Advanced Programmers
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.
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.
@Robson I am happy you found the tutorial useful :) thank you.
March 27, 2022, 4:06 p.m.
Nov. 22, 2021, 8:20 p.m.