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.

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

python3 manage.py makemigrations
python3 manage.py migrate

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.

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']

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

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.

Template

Let's create the form template file:

mkdir -p templates/blog
vim templates/blog/blog_form.html
<form method="POST" action="">
  {% csrf_token %}
  {{ form }}
  <input type="submit" value="Save">
</form>

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})

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
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 %}

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: