How to create a contact form with Django, widget customization

Quick tutorial for creating contact forms with Django CreateView, plus a bit of 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:

  1. the model to operate on
  2. a list of fields to expose in the HTML form
  3. 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:

  1. initial form display
  2. POST/error handling
  3. 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:

  1. create a form tag
  2. create a submit button
  3. render the Django form
  4. 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!

Resources

Valentino Gagliardi

Hi! I'm Valentino! I'm a freelance consultant with a wealth of experience in the IT industry. I spent the last years as a frontend consultant, providing advice and help, coaching and training on JavaScript, testing, and software development. Let's get in touch!

More from the blog: