WDTUTORIALS
Web & Game Development Tutorials
Unity, Django, Drupal, C#, Python…
menu
Samuli Natri 2017.09.27
Entrepreneur. Software developer since the 90's.
Attended Helsinki University Of Technology (Computer Science) and Helsinki University (Social Sciences).

Django - How To Create Forms With ModelForm

Tutorial on how to create forms from Models.

Check How To Create A Minimal Django Project tutorial on how create a basic project skeleton.

Create An App

Create an app called blog:

python3 manage.py startapp blog

Add blog to INSTALLED_APPS:

vim mysite/settings.py
INSTALLED_APPS = [
    ...
    'django.contrib.staticfiles',
    'blog', # < here
]

Create A Model

Edit blog/models.py and add a Blog class:

vim blog/models.py
class Blog(models.Model):
    title = models.CharField(max_length=255, default='', blank=True)
Name Description
max_length The only required argument for CharField
default="" This default value used for the field if none is provided when creating the object. It will also show in the form for the title field when creating a new Blog post.
blank=True This means that the form doesn't require an input for the field so we can leave it empty.

It is not recommended to use null=True for string-based fields like CharField.

Create new migration files based on the model and apply them:

python3 manage.py makemigrations
python3 manage.py migrate

Makemigrations packages model changes into migration files and migrate applies those changes.

Admin Area

Create a superuser:

python3 manage.py createsuperuser

Now if you go to the Admin area (/admin/), you can't see the Blog app yet.
 
Edit blog/admin.py and add these lines:

from .models import Blog

admin.site.register(Blog)

This allows us edit Blog items from the Admin interface.

Save and open the Admin area again:

Add some blog items.

Object string representations

Now the blog list looks something like this:

Let's show item titles by defining the __str__ method:

vim blog/models.py
class Blog(models.Model):
    title = models.CharField(max_length=255, blank=True, null=True)

    def __str__(self):
        return '%s' % self.title

The admin display is using str() function on the blog objects. This will call the __str__(` method which we can use to return a human readable representation of the object.

 % (modulo) is used here as string formatting operator. %s denotes the conversion type (string).

Refresh the admin page and you should see list of titles.

ModelForm

ModelForm class allows you to create forms from models.

Create forms.py file:

vim blog/forms.py
from django.forms import ModelForm
from .models import Blog

class BlogForm(ModelForm):
    class Meta:
        model = Blog
        fields = ['title']

The documentation recommends that you specify explicitly the fieldslike we did above. You can also safely use fields = '__all__' to use all fields from the model or exclude = ['description','date'] to use all fields but the ones excluded.

Urls

We need a path where to access the form. Create a urls.py file:

vim blog/urls.py
from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^add/blog/$', views.add_blog, name='add_blog'),
]

Open the main urls.py file and reference the blog app urls from there:

vim mysite/urls.py

Import include and add url(r'^', include('blog.urls')):

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^', include('blog.urls')), # < 1

Note that we don't add the $ sign to match the end of the string. If you want to define a base path, then add a trailing slash /: url(r'^moderators/', include('blog.urls')). In this case our form could be accessed in here: /moderators/add/blog/.

View

Let's create the add_blog function to handle requests to the add/blog path:

vim blog/views.py

views.py:

from django.shortcuts import render
from .forms import BlogForm

def add_blog(request):
    if request.method == "POST":
        form = BlogForm(request.POST)
        if form.is_valid():
            blog_item = form.save(commit=False)
            blog_item.save()
    else:
        form = BlogForm()
    return render(request, 'blog/blog_form.html', {'form': form})

 So when we go to the address add/blog we will see the unbound form with no data. If we input data and post the form, the request.method == "POST" will be True and we will validate and possibly create the item.

Django's Form class represents a form and determines how it works. Our BlogForm class is a ModelForm subclass which helps you create that Form class from Models.

Template

Let's create the form template file:

mkdir -p templates/blog
vim templates/blog/blog_form.html

-p creates the parent folder templates also.

<form method="POST" action="">
  {% csrf_token %}
  {{ form }}
  <input type="submit" value="Save">
</form>

Use the CSRF token inside form elements that uses POST method and targets internal paths.

Now you should be able to see the form in /add/blog/.

The generated markup will look something like this:

<form method="POST" enctype="multipart/form-data" action="">
  <input type="hidden" name="csrfmiddlewaretoken" value="FtrR243JfXvGf1SVebKHV5syAsOGqT9C8IdZD0Yoxus6thadT2tJxbMUDc3NJRWM">
  <label for="id_title">Title:</label><input type="text" name="title" id="id_title" maxlength="255">
  <input type="submit" value="Save">
</form>

Edit form

We can use the same form template for editing content. Let's add another pattern in the blog app urls.py file:

vim blog/urls.py

urls.py:

url(r'^add/blog/$', views.add_blog, name='add_blog'),
url(r'^edit/blog/(?P<id>\d+)/$', views.edit_blog, name='edit_blog'),

Add the edit_blog function in the blog app views.py:

vim blog/views.py
from django.shortcuts import render, get_object_or_404
from .forms import BlogForm
from .models import Blog

def add_blog(request):
    if request.method == "POST":
        form = BlogForm(request.POST)
        if form.is_valid():
            blog_item = form.save(commit=False)
            blog_item.save()
    else:
        form = BlogForm()
    return render(request, 'blog/blog_form.html', {'form': form})


def edit_blog(request, id=None):
    item = get_object_or_404(Blog, id=id)
    form = BlogForm(request.POST or None, instance=item)
    if form.is_valid():
        form.save()
    return render(request, 'blog/blog_form.html', {'form': form})

Now you can add blog posts in /add/blog and edit them in /edit/blog/[id].

Add fields manually

You can also add the fields manually. Let's add a description field to the model:

vim blog/models.py
class Blog(models.Model):
    title = models.CharField(max_length=255, default='', blank=True)
    description = models.TextField(default='', blank=True) # < here

TextField is a *large* text field.

vim blog/forms.py
class BlogForm(ModelForm):
    class Meta:
        model = Blog
        fields = ['title', 'description'] # < here
python3 manage.py makemigrations
python3 manage.py migrate
vim blog/templates/blog/blog_form.html

Let's change the order of the fields:

<form method="POST" enctype="multipart/form-data" action="">
  {% csrf_token %}
  {{ form.non_field_errors }}
  <div class="field_wrapper">
    {{ form.description.errors }}
    <label for="{{ form.description.id_for_label }}">Description:</label>
    {{ form.description }}
  </div>
  <div class="field_wrapper">
    {{ form.title.errors }}
    <label for="{{ form.title.id_for_label }}">Title:</label>
    {{ form.title }}
  </div>
  <input type="submit" value="Save">
</form>

Redirect and blog page

Let's create a page for blog posts and redirect the user to that page after adding / editing an object:

Redirect

vim blog/views.py

views.py:

from django.shortcuts import render, get_object_or_404, redirect # < 1
from .forms import BlogForm
from .models import Blog

def add_blog(request):
    if request.method == "POST":
        form = BlogForm(request.POST)
        if form.is_valid():
            blog_item = form.save(commit=False)
            blog_item.save()
            return redirect('/blog/' + str(blog_item.id) + '/') # < 1
    else:
        form = BlogForm()
    return render(request, 'blog/blog_form.html', {'form': form})
    
def edit_blog(request, id=None):
    item = get_object_or_404(Blog, id=id)
    form = BlogForm(request.POST or None, instance=item)
    if form.is_valid():
        form.save()
        return redirect('/blog/' + str(item.id) + '/') # < 1
    return render(request, 'blog/blog_form.html', {'form': form})

Templates

Create base.html and blog.html templates:

vim blog/templates/blog/base.html

base.html:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ModelForm tutorial | WDTutorials.com</title>
</head>
<body>

{% block content %} {% endblock %} <!-- 1 -->
 
</body>
</html>

This is the basic layout skeleton for the site.

vim blog/templates/blog/blog.html

blog.html:

{% extends 'blog/base.html' %} <!-- 1 -->

{% block content %} <!-- 2 -->
<h1>{{ blog.title }}</h1> <!-- 3 -->
<p>{{ blog.description }}</p>
{% endblock %}

When the blog template is evaluated, system locates the parent template (base.html) and replace the block contents with the contents of the child template.


Url

vim blog/urls.py
from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^add/blog/$', views.add_blog, name='add_blog'),
    url(r'^edit/blog/(?P<id>\d+)/$', views.edit_blog, name='edit_blog'),
    url(r'^blog/(?P<id>\d+)/$', views.blog, name='blog'),  # < 1
]

View

vim blog/views.py

Add the blog function:

def blog(request, id=id):
    blog = Blog.objects.get(id=id)
    return render(request, 'blog/blog.html', {'blog': blog })

So now you will be redirected to the blog page in /blog/<id>/ after you add/edit blog.

Links

Using ModelForm is one way to create forms. Check Django documentation for more info: