Render Django Form with Tailwind CSS Styles
Objectives
- Render Django form with Tailwind CSS
Django App
Let's create a turbo_drive
app, we will learn how Turbo Drive
works with this Django app.
(venv)$ mkdir -p ./hotwire_django_app/turbo_drive
(venv)$ python manage.py startapp turbo_drive ./hotwire_django_app/turbo_drive
We will have structure like this:
├── hotwire_django_app
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py
│ ├── tasks
│ ├── templates
│ ├── turbo_drive # new
│ │ ├── __init__.py
│ │ ├── admin.py
│ │ ├── apps.py
│ │ ├── migrations
│ │ ├── models.py
│ │ ├── tests.py
│ │ └── views.py
│ ├── urls.py
│ └── wsgi.py
Update hotwire_django_app/turbo_drive/apps.py to change the name to hotwire_django_app.turbo_drive
from django.apps import AppConfig
class TurboDriveConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'hotwire_django_app.turbo_drive' # update
Add hotwire_django_app.turbo_drive
to the INSTALLED_APPS
in hotwire_django_app/settings.py
INSTALLED_APPS = [
...
'hotwire_django_app.turbo_drive', # new
]
# check if there is any error
(venv)$ ./manage.py check
System check identified no issues (0 silenced).
View
Create hotwire_django_app/turbo_drive/views.py
from django.shortcuts import render, redirect
from hotwire_django_app.tasks.forms import TaskForm
def create_view(request):
if request.method == 'POST':
form = TaskForm(request.POST)
if form.is_valid():
form.save()
return redirect('/')
else:
form = TaskForm()
return render(request, 'turbo_drive/create.html', {'form': form})
Here we create a simple Django FBV (function-based view), user can create tasks on the form page.
Template
Create hotwire_django_app/templates/turbo_drive/base.html
{% load webpack_loader static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% stylesheet_pack 'turbo_drive' %}
{% javascript_pack 'turbo_drive' attrs='defer' %}
</head>
<body>
{% block content %}
{% endblock content %}
</body>
</html>
Notes:
- We already created
turbo_drive
JS application file and CSS file in the previous chapter, we import them to the Django template here.
Create hotwire_django_app/templates/turbo_drive/create.html
{% extends "turbo_drive/base.html" %}
{% block content %}
<div class="w-full max-w-7xl mx-auto px-4">
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn-blue">Submit</button>
</form>
</div>
{% endblock %}
URL
Create hotwire_django_app/turbo_drive/urls.py
from django.urls import path
from .views import create_view
app_name = 'turbo-drive'
urlpatterns = [
path('create/', create_view, name='task-create'),
]
Notes:
- Here we use the
app_name
to set thenamespace
of the Django app.
Update hotwire_django_app/urls.py
from django.contrib import admin
from django.urls import path, include # update
from django.views.generic import TemplateView
urlpatterns = [
path('', TemplateView.as_view(template_name="index.html")),
path('turbo-drive/', include('hotwire_django_app.turbo_drive.urls')), # new
path('admin/', admin.site.urls),
]
Manual test
# make sure 'npm run start' is running
(venv)$ python manage.py runserver
If we check on http://127.0.0.1:8000/turbo-drive/create/
As you can see, even if we import Tailwind to our Django project, the default form style still look not good.
Next, let's start improving the form style.
tailwindcss-forms
tailwindcss/forms is a plugin that provides a basic reset for form styles that makes form elements easy to override with utilities.
$ npm install @tailwindcss/forms
In the package.json
, we can see
"@tailwindcss/forms": "^0.5.3",
Update tailwind.config.js
to use the plugin.
module.exports = {
//
plugins: [
require('@tailwindcss/forms'), // new
],
}
Hmm, the form style looks much better, let's keep improving it.
crispy-tailwind
crispy-tailwind is a Tailwind template pack for django-crispy-forms
UPDATE: crispy-tailwind
is not recommended anymore, please check django-formify, which seamlessly integrates Tailwind CSS styles into your Django forms for a modern look.
Update requirements.txt
django-crispy-forms==2.0 # new
crispy-tailwind==0.5.0 # new
(venv)$ pip install -r requirements.txt
Update hotwire_django_app/settings.py
INSTALLED_APPS = [
...
'crispy_forms', # new
'crispy_tailwind', # new
]
CRISPY_ALLOWED_TEMPLATE_PACKS = "tailwind" # new
CRISPY_TEMPLATE_PACK = "tailwind" # new
Update hotwire_django_app/templates/turbo_drive/create.html
{% extends "turbo_drive/base.html" %}
{% load crispy_forms_tags %}
{% block content %}
<div class="w-full max-w-7xl mx-auto px-4">
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="btn-blue">Submit</button>
</form>
</div>
{% endblock %}
Notes:
- We load
crispy_forms_tags
at the top {{ form|crispy }}
will render the form using Tailwind template pack. (set byCRISPY_TEMPLATE_PACK
)
JIT
If we check the form style.
We notice some tailwind css such as mb-2
is still not working.
Why did it happen?
Because we did not tell Tailwind CSS which css classes are used by crispy-tailwind
If you use other 3-party Python packages to manipulate tailwind css classnames, you might also meet this problem
Update tailwind.config.js
const Path = require("path");
const pwd = process.env.PWD;
// To make tailwind can scan code in Python packages:
// export pySitePackages=$(python3 -c "import sysconfig; print(sysconfig.get_path('purelib'))")
const pySitePackages = process.env.pySitePackages;
// We can add current project paths here
const projectPaths = [
Path.join(pwd, "./hotwire_django_app/templates/**/*.html"),
// add js file paths if you need
];
// We can add 3-party python packages here
let pyPackagesPaths = []
if (pySitePackages){
pyPackagesPaths = [
Path.join(pySitePackages, "./crispy_tailwind/**/*.html"),
Path.join(pySitePackages, "./crispy_tailwind/**/*.py"),
Path.join(pySitePackages, "./crispy_tailwind/**/*.js"),
];
}
const contentPaths = [...projectPaths, ...pyPackagesPaths];
console.log(`tailwindcss will scan ${contentPaths}`);
module.exports = {
content: contentPaths,
theme: {
extend: {},
},
plugins: [
require('@tailwindcss/forms'),
],
};
If we set pySitePackages
env variable, the Tailwind can know the path of the crispy_tailwind
package, and will scan the code to detect what css class names are used.
(venv)$ python3 -c "import sysconfig; print(sysconfig.get_path('purelib'))"
hotwire_django_project/env/lib/python3.10/site-packages
# set it to pySitePackages ENV variable
(venv)$ export pySitePackages=$(python3 -c "import sysconfig; print(sysconfig.get_path('purelib'))")
# check
(venv)$ env | grep pySitePackages
# restart webpack
(venv)$ npm run start
As you can see, now the form style works with Tailwind smoothly
Test Again
Now, please try to create some tasks on the form page, and you will be redirected to the home page.
If the title is very short, you might see Error: Form responses must redirect to another location
in the console of the web devtool, do not worry, and I will talk about it in the next chapter.