Getting Started With Django Forms

Dayne Dayne (57)
0

Django forms are an excellent way to manage user input in a Django application. Once you learn how to wield them, you'll never go back to manually coding a form tag and handling the response. This guide will scratch the surface but get you far enough to begin using and learning the power of Django's Form class.

Let's dive in.

Posted in these interests:
h/django6 guides
h/python67 guides

Django forms save time and code. Because of their class based nature, you are able to reuse forms saving you time and complexity.

Using a mature web framework's form class also enforces more secure code than you write (probably). For example, Django forms will complain loudly if you do not use CSRF protection, something that can easily go forgotten if you're manually coding a form.

Django's Form class is the heartbeat of Django's form components. In the same way Django's Model class describes an object, the Form class describes a form. A Model's fields describe individual database columns and a Form's fields describe individual form elements.

class ArticleForm(forms.ModelForm):
    title = forms.CharField()
    desc = forms.TextField()

    class Meta:
        model = Article
  1. create a form class for your needs (usually in forms.py)
  2. instantiate that form class (usually in a view)
  3. render that form (usually in a template)
  4. instantiate the same form class with POST data (aka bind data to the form)

There are other minor steps which we will cover, but this is the basic framework for using Django forms.

Create a form class. This class represents a contact form that takes name, email, and message inputs. Django chooses an HTML element based on the type of field you specify. You can also override this choice by passing in a 'widget' argument.

from django import forms

class ContactForm(forms.Form):
    name = forms.CharField()
    email = forms.EmailField()
    message = forms.CharField(widget=forms.Textarea)

Create a view

# at the top of the file
from .forms import ContactForm

def contact(request):
    contact_form = ContactForm()
    return render(request, "contact.html", {"form": contact_form})

Setup a new url

url(r'^contact/?$', contact, name='contact')

Create a template

<h1>Contact</h1>
<form role="form" action="" method="post">
    {% csrf_token %}
    {{ form }}
    <button type="submit">Submit</button>
</form>

Handle the POST request in the view

if request.method == 'POST':
    contact_form = ContactForm(data=request.POST)

    if contact_form.is_valid():
        name = request.POST.get('name', '')
        email = request.POST.get('email', '')
        message = request.POST.get('message', '')
        # do some processing with name and email
        messages.success(request, 'Your message has been sent!')
        return redirect('contact')
else:
    contact_form = ContactForm()

Create a model

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=1000)
    desc = models.CharField(max_length=1000)

# makemigrations && migrate

Create a form

from django import forms

from .models import Article

class ArticleForm(forms.ModelForm):
   class Meta:
       model = Article
       fields = ['title', 'desc',]

Create a view

# at the top of the file
from .forms import ArticleForm

def article_create(request):
    article_form = ArticleForm()
    return render(request, "article_form.html", {"form": article_form})

Setup a new url

url(r'^articles/create?$', contact, name='article_create')

Create a template

<h1>Article Form</h1>
<form role="form" action="" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Submit</button>
</form>

Update description field widget

class ArticleForm(forms.ModelForm):
    class Meta:
        model = Article
        fields = ['title', 'desc',]
        widgets = {
                'desc': forms.Textarea(attrs={'class': 'desc'}),
        }

Handle the POST request in the view

if request.method == 'POST':
    article_form = ArticleForm(data=request.POST)

    if article_form.is_valid():
        article = article_form.save()
        return redirect('article-detail', pk=article.pk)
else:
    article_form = ArticleForm()

Create a view

def article_edit(request, pk):
    try:
        article = Article.objects.get(pk=pk)
    except Article.DoesNotExist:
        return redirect('article-create')

    article_form = ArticleForm(instance=article)

    return render(request, "article_form.html", {"form": article_form})

Setup a new url

url(r'^articles/(?P<pk>\d+)/edit?$', contact, name='article-edit')

Handle the POST request in the view

if request.method == 'POST':
    article_form = ArticleForm(instance=article, data=request.POST)

    if article_form.is_valid():
        article = article_form.save()
        return redirect('article-detail', pk=article.pk)
else:
    article_form = ArticleForm(instance=article)

Custom Clean Method

def clean_title(self):
    title = self.cleaned_data.get('title', None)

    if 'django' not in title.lower():
        raise forms.ValidationError("You forgot to talk about Django!")

    return title

Different ways to display a form in HTML

Default or .as_table()

article_form = ArticleForm()
print(article_form) # or print(article_form.as_table())

# results 

<tr>
<th><label for="id_title">Title:</label></th>
<td><input id="id_title" maxlength="1000" name="title" type="text" /></td>
</tr>
<tr>
<th><label for="id_desc">Desc:</label></th><td>
<textarea class="desc" cols="40" id="id_desc" maxlength="1000" name="desc" rows="10"></textarea></td>
</tr>

.as_p()

article_form = ArticleForm()
print(article_form.as_p())

#results

<p>
<label for="id_title">Title:</label> 
<input id="id_title" maxlength="1000" name="title" type="text" />
</p>
<p>
<label for="id_desc">Desc:</label>
<textarea class="desc" cols="40" id="id_desc" maxlength="1000" name="desc" rows="10"></textarea>
</p>

article_form.as_ul()

article_form = ArticleForm()
print(article_form.as_ul())

#results

<li>
<label for="id_title">Title:</label>
<input id="id_title" maxlength="1000" name="title" type="text" />
</li>
<li>
<label for="id_desc">Desc:</label>
<textarea class="desc" cols="40" id="id_desc" maxlength="1000" name="desc" rows="10"></textarea>
</li>

In Python

article_form = ArticleForm()
for field in article_form:
    print(field)

# results
<input id="id_title" maxlength="1000" name="title" type="text" />
<textarea class="desc" cols="40" id="id_desc" maxlength="1000" name="desc" rows="10"></textarea>

In Template

{% for field in form %}
    <div class="fieldWrapper">
        {{ field.errors }}
        {{ field.label_tag }}
    {{ field }}
    {% if field.help_text %}
            <p class="help">{{ field.help_text|safe }}</p>
    {% endif %}
</div>
{% endfor %}

Pros of Django Forms

  1. Huge timesaver and code saver
  2. Very powerful
  3. Well documented
  4. Good community support

Weaknesses

  1. There is a lot of configuration
  2. You will spend tons of time in the docs at first
  3. You will run into trouble trying to customize the display of your form fields (crispy forms helps)
  4. Django does nothing with Javascript so for super Javascripty forms, you're on your own

A word about Crispy Forms

• Extremely customizable form output • Helps keep code DRY • Sticks to Django conventions so it does not feel like a 3rd party package • https://github.com/maraujop/django-crispy-forms

John John (304)
2 minutes

By default, Django migrations are run only once. But sometimes we need to rerun a Django migration, especially when testing custom migrations during development.