How to create a contact form with Django, widget customization
Let's say you want to create a contact form with Django to get contacts for your bookshop (or library, who knows). With Django, the quickest way to get up and running is a CreateView.
Let's see how it works, and how to customize form fields with Django widgets.
Creating the project
To follow along make sure to create a new Django project. With the project in place create a Django app named library:
django-admin startapp library
Next up enable the app in settings.py
:
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
# enable the app
"library.apps.LibraryConfig",
]
Now create a model in library/models.py
:
from django.db import models
class Contact(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
message = models.TextField(max_length=400)
def __str__(self):
return f"{self.first_name} {self.last_name}"
Then run and apply the migration:
python manage.py makemigrations library
python manage.py migrate
With the model in place we're ready to wire things up.
Wiring up views and URLs
Create a new file for URLs in library/urls.py
. In this file we define two paths with the corresponding names. "contact" will show the form, "thanks" will show a message for our users:
from django.urls import path
from .views import ContactCreate, thanks
urlpatterns = [
path("contact/", ContactCreate.as_view(), name="contact"),
path("thanks/", thanks, name="thanks"),
]
Now let's pull in a CreateView. Create a new file in library/views.py
:
from django.views.generic import CreateView
from .models import Contact
from django.urls import reverse_lazy
from django.http import HttpResponse
class ContactCreate(CreateView):
model = Contact
fields = ["first_name", "last_name", "message"]
success_url = reverse_lazy("thanks")
def thanks(request):
return HttpResponse("Thank you! Will get in touch soon.")
Here we import a Django CreateView, a generic view which offers a nice abstraction over the quintessential POST/Redirect/GET pattern.
Next up we import our Contact model, a function called reverse_lazy
, and HttpResponse
. To return a template we could also use render
, but for the scope of this post we're fine.
Digging deeper into CreateView
Worth spending a couple of words on CreateView. It takes at least three attributes:
- the model to operate on
- a list of fields to expose in the HTML form
- a success url to redirect the user to
success_url
could be replaced with a model method called get_absolute_url
. In this example we want to redirect the user to a custom page, reverse_lazy
with the view name does the trick.
get_absolute_url
instead is convenient when you want to redirect the user to the newly created model instance.
From CreateView
you get for free:
- initial form display
- POST/error handling
- redirect to a success url.
Let's render the form in the template now.
Rendering the form with CreateView
CreateView looks by default for a template to render the form in which follows the "modelname_form" convention.
That means you have to create a new template in library/templates/library/contact_form.html
(pay attention to the nested structure).
In this template we need to:
- create a form tag
- create a submit button
- render the Django form
- provide the csrf_token
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Contact me!</title>
</head>
<body>
<form method="POST" action="{% url "contact" %}">
{% csrf_token %}
{{ form }}
<button type="submit">SEND</button>
</form>
</body>
</html>
You have also some choice for rendering the form in the template.
What matters for the scope of this tutorial is that each form input is a widget for Django. If you want to customize the attributes of one or more inputs you need to create a custom ModelForm.
Let's see how.
Widget customization
ModelForm in Django is a convenient class for creating HTML forms from models. Inside this class you can customize the fields, called widgets. First thing first create a new form in library/forms.py
:
from django.forms import ModelForm
from django.forms import Textarea
from .models import Contact
class ContactForm(ModelForm):
class Meta:
model = Contact
fields = ["first_name", "last_name", "message"]
widgets = {
"message": Textarea(
attrs={
"placeholder": "Would love to talk about Philip K. Dick"
}
)
}
In the ModelForm meta we specify the model, the fields to expose, and any customization for our HTML fields. In this case I want to customize the textarea placeholder.
Now in library/views.py
we configure CreateView
to use ContactForm
instead of the default form:
from django.views.generic import CreateView
from .models import Contact
from django.urls import reverse_lazy
from django.http import HttpResponse
from .forms import ContactForm
class ContactCreate(CreateView):
model = Contact
form_class = ContactForm
success_url = reverse_lazy("thanks")
def thanks(request):
return HttpResponse("Thank you! Will get in touch soon.")
Notice that we defined the fields in ContactForm
, and not in CreateView
anymore.
With this setup we get a production ready HTML form complete with validation and error handling.
Thanks for reading!