WDTUTORIALS

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 fields like 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'),
]
Name Description
url This function is used to instantiate patterns.
urlpatterns List of pattern instances.
r'^add/blog/$ A string that should contain a regular expression.
r Raw string syntax allows using backslash sequences like \d in the string without having to escape them with another backslash.
^ Caret matches the start of the string.
$ Dollar matches the end of the string. If we don’t add it then this pattern would match everything starting with add/blog/ like add/blog/random/10
views.add_blog The function that handles requests to this url (yet to be implemented).
name='add_blog' Name of the pattern. This can be used elsewhere like in templates to avoid hard coding urls.

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
]
Name Description
1. include This function includes the url patterns from the Blog app urls.py.

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
from django.shortcuts import render # < 1
from .forms import BlogForm 

def add_blog(request): # < 2
    if request.method == "POST": # < 3
        form = BlogForm(request.POST) # < 4
        if form.is_valid(): # < 5
            blog_item = form.save(commit=False) # < 6, 7
            blog_item.save()
    else:
        form = BlogForm() # < 8
    return render(request, 'blog/blog_form.html', {'form': form}) # < 9
Name Description
1. django.shortcut provides general helper functions and classes like the render function.
2. request When you request a page, an object is created and passed to the view. This contains metadata about the request like the method used and the form data.
3. POST POST method is used for more sensitive data like passwords where the data is sent to the server encoded. GET method would be used in something more insensitive like search queries: mysite/?search=django. This information would be visible in the url and saved in the browser history.
4. form = BlogForm(request.POST) This creates the form instance and bounds form data to it. Request.POST contains the form data.
5. form.is_valid() is_valid method runs validation against the fields and returns either False or True.
6. form.save(commit=False) ModelForm has a save method that creates and saves an object bound to the form.
7. commit=False means that we return an object that has not been stored in the database yet. That’s why have to run save() against the object to actually save it to the database.
8. form = BlogForm() This creates an unbound form instance. Because we don’t have POST data available, we have no data to bound to the form. This will result in a empty form when it is rendered with the render function.
9. render This function returns a HttpResponse object. This will contain the content for the page. We are declaring the template file and the form variable to be used in that template.

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=""> <!-- 1 -->
  {% csrf_token %} <!-- 2 -->
  {{ form }} <!-- 3 -->
  <input type="submit" value="Save">
</form>
Name Description
1. action="" Empty action means that we are sending the form POST data to the current url (add/blog/).
2. {% csrf_token %} This token provides protection for Cross Site Request Forgery
3. {{ form }} This renders the form fields with their labels and inputs. There are some other output options like {{ form.as_p }} that wraps the label / input pairs in p tags.

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
    url(r'^add/blog/$', views.add_blog, name='add_blog'),
    url(r'^edit/blog/(?P<id>\d+)/$', views.edit_blog, name='edit_blog'), # < 1
Name Description
1. (?P<id>\d+)/$ This is a named regular-expression group that is used like this: (?P<name>pattern)
?P<id> This means that we are sending a variable named id to the view.
\d+ \d matches digit characters and + matches 1 or more of those like 1 or 102.

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

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

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): # < 2
    item = get_object_or_404(Blog, id=id) # < 3
    form = BlogForm(request.POST or None, instance=item) # < 4, 5
    if form.is_valid():
        form.save()
    return render(request, 'blog/blog_form.html', {'form': form})
Name Description
1. get_object_or_404 and Blog Make sure to import these
2. id=None None is the default value for the id argument.
3. get_object_or_404 This returns an object with the given id or raise a Http404 error if the object doesn’t exist.
4. request.POST or None If there is POST data available, we use that data (eg. if we change data and hit save).
5. instance=item If existing item is provided through the instance argument, then save method will update that instance instead of creating a new one. Also the form is initially populated with the data from the existing object.

So, 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 }} <!-- 1 -->
  <div class="field_wrapper">
    {{ form.description.errors }} <!-- 2 -->
    <label for="{{ form.description.id_for_label }}">Description:</label> <!-- 3 -->
    {{ form.description }} <!-- 4 -->
  </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>

Name Description
1. form.non_field_errors Prints out errors that are not related to any specific field.
2. form.description.errors Prints out field specific errors.
3. form.description.id_for_labels Prints out id of the field for the label “for” attribute.
4. form.description Prints out the field.

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
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})
Name Description
1. Redirect This function does the redirecting for us.

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.

Name Description
1. {% block content %} {% endblock %} Defines a block that child templates can override.
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 %}

Name Description
1. {% extends 'blog/base.html' %} Defines the parent template.
2. {% block content %} {% endblock %} Content inside this block will dynamically replace the content block in the parent template.
3. {{ blog.title }} You can access attributes with a dot .

So, 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
]
Name Description
1. id Added url pattern takes id as an argument like in the edit/blog pattern.

View

vim blog/views.py

Add the blog function:

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

In here we do a 1. database query to fetch a Blog object based on the primary key and 2. pass it in the template as a variable called 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:

Samuli Natri built his first website in the late 90’s. He is very passionate about web technologies. He attended Helsinki University Of Technology (Computer Science) and Helsinki University (Social Sciences). He is the founder of WDTutorials.com.

Featured Python Tutorial For Beginners - Essentials In 1 Hour

Copyright @ 2018 WDTutorials.com. All rights reserved. Privacy policy.Terms Of Service.