Django - ModelForm Tutorial
How to create forms with ModelForm.
Updated Jun 21, 2020

Quickstart

blog/forms.py

Create a file called forms.py in the blog app directory and add these lines to it:

from django.forms import ModelForm
from .models import Post

class PostForm(ModelForm):
    class Meta:
        model = Post
        fields = ['title']

class PostDeleteForm(ModelForm):
    class Meta:
        model = Post
        fields = []

mysite/urls.py

Edit the main urls.py file and add these lines to it:

from django.contrib import admin
from django.urls import path
from blog.views import post_create, post_edit, post_delete

urlpatterns = [
    path('blog/create/', post_create, name='post_create'),
    path('blog/edit/<int:pk>/', post_edit, name='post_edit'),
    path('blog/delete/<int:pk>/', post_delete, name='post_delete'),
    path('admin/', admin.site.urls),
]

blog/views.py

Edit the views.py file and add these function to it:

from django.shortcuts import get_object_or_404, redirect, render
from .forms import PostForm, PostDeleteForm
from blog.models import Post


def post_create(request):
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('post_create')
    else:
        form = PostForm()
    return render(request,
                  'blog/post_create.html',
                  {
                      'form': form
                  })


def post_edit(request, pk=None):
    post = get_object_or_404(Post, pk=pk)
    if request.method == "POST":
        form = PostForm(request.POST,
                        instance=post)
        if form.is_valid():
            form.save()
            return redirect('post_create')
    else:
        form = PostForm(instance=post)

    return render(request,
                  'blog/post_edit.html',
                  {
                      'form': form,
                      'post': post
                  })


def post_delete(request, pk=None):
    post = get_object_or_404(Post, pk=pk)
    if request.method == "POST":
        form = PostDeleteForm(request.POST,
                              instance=post)
        if form.is_valid():
            post.delete()
            return redirect('post_create')
    else:
        form = PostDeleteForm(instance=post)

    return render(request, 'blog/post_delete.html',
                  {
                      'form': form,
                      'post': post,
                  })

Templates

Add these files in the blog/templates/blog directory:

post_create.html

<h1>Add new post</h1>
<form action="{% url 'post_create' %}"
      method="post">
      {% csrf_token %}
      {{ form.as_p }}
<button class="button" type="submit">Create</button>
</form>

post_edit.html

<h1>Edit post</h1>
<form action="{% url 'post_edit' post.pk %}"
      method="post">
      {% csrf_token %}
      {{ form.as_p }}
<button class="button" type="submit">Update</button>
</form>

post_delete.html

<h1>Delete post</h1>
<form action="{% url 'post_delete' post.pk %}" method="post">
    {% csrf_token %}
    {{ form }}
    Are you sure you want to delete the following post?<br><br>
    {{ post.title }}<br><br>
    <button class="button" type="submit">Delete</button>
    <a href="{% url 'post_create' %}">Cancel</a>
</form>

Full Tutorial

This tutorial shows how to use function-based views to create, edit and delete objects.

Note: Django's generic class-based editing views (like CreateView) offer more abstracted (and easier) solution.

Setup

Run these commands to setup a project:

mkdir mysite && cd mysite
python3 -m venv venv
source venv/bin/activate 
pip install django
django-admin startproject mysite .
python manage.py startapp blog

Edit the project settings.py file and add the blog app configuration class to the INSTALLED_APPS list:

INSTALLED_APPS = [
    # HERE
    'blog.apps.BlogConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Add a class called Post to the blog app models.py file:

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=255,
                             unique=True)

Run migrations:

python manage.py makemigrations && \
python manage.py migrate

Create form

Create a file called forms.py in the blog app directory and add these lines to it:

from django.forms import ModelForm
from .models import Post

class PostForm(ModelForm):
    class Meta:
        model = Post
        fields = ['title']
  • ModelForm is a helper class that allows us to create forms using existing models.
  • Use the fields attribute to define what fields the form should have.

Edit the main urls.py file and add these lines to it:

from django.contrib import admin
from django.urls import path

# HERE
from blog.views import post_create

urlpatterns = [
    # HERE
    path('blog/create/', 
         post_create, 
         name='post_create'),
    path('admin/', admin.site.urls),
]

Edit the blog app views.py file and add a function called post_create to it:

from django.shortcuts import render, redirect
from .forms import PostForm
from blog.models import Post

def post_create(request):
    if request.method == 'POST':
        form = PostForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('post_create')
    else:
        form = PostForm()
    return render(request,
                  'blog/post_create.html',
                  {
                      'form': form
                  })
  • The if request.method == 'POST': line checks if a form has been posted using the POST method.
  • The form object is created from the request data using the PostForm class we created earlier.
  • If the form validation succeeds, we save the object to the database using form.save().
  • The else clause is executed when we visit the blog/create/ URL without posting the form. We need to create the form object even if we are not posting any data so that the empty HTML form can be rendered on the page.
  • The form object is passed to the template in the render() function context. A context is a dictionary that maps template variable names to Python objects.

Create a file called post_create.html in the blog/templates/blog directory and add these lines to it:

<h1>Add new post</h1>
<form action="{% url 'post_create' %}"
      method="post">
      {% csrf_token %}
      {{ form.as_p }}
<button class="button" type="submit">Create</button>
</form>
  • The csrf_token provides protection against Cross Site Request Forgeries.
  • {{ form.as_p }} renders the form fields using <p> tags.

Visit /blog/create/ to create items:

Edit form

Edit the project urls.py file and add the following path to it:

# HERE
from blog.views import post_create, post_edit

urlpatterns = [
    path('blog/create/', 
         post_create, 
         name='post_create'),
    # HERE
    path('blog/edit/<int:pk>/', 
         post_edit, 
         name='post_edit'),
    path('admin/', admin.site.urls),
]

Edit the blog app views.py file and add a function called post_edit to it:

from django.shortcuts import render, redirect, get_object_or_404

def post_edit(request, pk=None):
    post = get_object_or_404(Post, pk=pk)
    if request.method == "POST":
        form = PostForm(request.POST,
                        instance=post)
        if form.is_valid():
            form.save()
            return redirect('post_create')
    else:
        form = PostForm(instance=post)

    return render(request,
                  'blog/post_edit.html',
                  {
                      'form': form,
                      'post': post
                  })
  • The get_objects_or_404() function is used to find the blog post we want to edit using its primary key. It returns 404 Not Found error if the object doesn't exist.
  • We pass in the post object to the PostForm class using the instance=post parameter. This makes the form.save() method update the object. Without it the method would create a new object, as happens with the post_create view.
  • The instance=post in the else clause populates the HTML form with the object data when we visit the page without sending a POST request.

Create a file called post_edit.html in the blog/templates/blog directory and add these lines to it:

<h1>Edit post</h1>
<form action="{% url 'post_edit' post.pk %}"
      method="post">
      {% csrf_token %}
      {{ form.as_p }}
<button class="button" type="submit">Update</button>
</form>

Visit /blog/edit/<id>/ to edit an item:

Delete form

Edit the blog/forms.py file and add a class called PostDeleteForm to it:

from django.forms import ModelForm
from .models import Post

class PostForm(ModelForm):
    class Meta:
        model = Post
        fields = ['title']

# START
class PostDeleteForm(ModelForm):
    class Meta:
        model = Post
        fields = []
# END

Edit the urls.py file and add the following path to it:

from django.contrib import admin
from django.urls import path

# HERE
from blog.views import post_create, post_edit, post_delete

urlpatterns = [
    path('blog/create/', 
         post_create, 
         name='post_create'),
    path('blog/edit/<int:pk>/', 
         post_edit, 
         name='post_edit'),
    # HERE
    path('blog/delete/<int:pk>/', 
         post_delete, 
         name='post_delete'),
    path('admin/', admin.site.urls),
]

Edit the blog app views.py file and add a function called post_delete to it:

from .forms import PostForm, PostDeleteForm

def post_delete(request, pk=None):
    post = get_object_or_404(Post, pk=pk)
    if request.method == "POST":
        form = PostDeleteForm(request.POST,
                              instance=post)
        if form.is_valid():
            post.delete()
            return redirect('post_create')
    else:
        form = PostDeleteForm(instance=post)

    return render(request, 'blog/post_delete.html',
                  {
                      'form': form,
                      'post': post,
                  })
  • The post_delete view is very similar to the post_edit view but instead of updating the blog post, we delete it using post.delete().

Create a file called post_delete.html in the blog/templates/blog directory and add these lines to it:

<h1>Delete post</h1>
<form action="{% url 'post_delete' post.pk %}" method="post">
    {% csrf_token %}
    {{ form }}

    Are you sure you want to delete the following post?:<br><br>
    {{ post.title }}?<br><br>
    
    <button class="button" type="submit">Delete</button>
    <a href="{% url 'post_create' %}">Cancel</a>

</form>

Visit blog/delete/<id>/ to delete an item: