Django Tutorial Part 8: Creating ModelForms

Disclaimer: Your support helps keep JovialGuide running! Our content is reader-supported. This means if you click on some of our links, we may earn a commission.

Part 7 of this complete beginners guide to Django showed you how to create views – creating the list, detail, update and the delete views. In this complete beginners guide to Django part 8, we will show you how to create modelforms, then complete the create and edit views and template code for our articles and categories. This is a complete beginners guide to Django, so if you haven’t read part 7, then see part 7: creating views or see part 1 – introduction to Django to get started.

What is a ModelForm?

A form is a field for collecting user inputs. For example; when you have to register to a site, you will have to fill your name, email and password to join. This means that the fields for signing-up are a form. Another example is when you have to leave a comment on a site. You will have to fill your details into the boxes. So, the comment fields are a form.

Django allows you to create forms in two ways, basically by sub-classing the:

  • form.Forms class or
  • form.ModelForm class

In this complete beginners guide to Django, we will focus on creating modelforms.

A ModelForm in Django is a class that helps you create a form from an existing Django model. It is a helper class that converts Django model fields into an actual form fields. When creating HTML forms, you will typically define the fields you want just like when you are creating models.

Django modelform allows you easily create forms from model fields. This means that an article model and an article form would use the same fields. Instead of creating a Django model and later creating a form separately, Django introduces modelforms which allows you easily create forms from models or convert models into forms.

How to Create ModelForms (Syntax)

Django forms are created in a forms.py file. This Python file basically doesn’t come with the startproject or startapp command, so you will have to create it yourself.

It is best you create a forms.py file for each Django app you start.

The way Django creates a form from a model is by using Python classes which subclasses the forms.ModelForm class. Notice that it is similar to models subclass, except that models subclass the models.Model class. See creating models for more.

Let’s look at the syntax to create modelforms:

# import forms
from django import forms

# import the model you want to create a form from
from .models import ModelName


class NameOfModelForm(forms.ModelForm):

  class Meta:
    # specify the name of model to use and the fields

    model = ModelName
    fields = "__all__"

  • To create a modelform, start by creating a forms.py file in your app directory.
  • Next import forms from django. After that, import the model that you want to convert to forms. Notice the empty space between from django import forms and models import. This is to separate Django import from our app imports.
  • Create a Python class that subclasses forms.ModelForm. Notice that the name of the form class has NameOfModelForm (in PascalCase) as its name. This means that your modelform should start with the name of the model you are creating a form from, followed by Form, which shows that it is a form.
  • Inside the form class, we created another class called Meta.
  • Inside the Meta class, we created a model property – which specifies the model we want to create a form from. This Python property accepts an actual model name. After the model property, we created another property called fields. The fields property is responsible for specifying the fields to include in the model form. In our example, we used __all__ (remember to quote), which means all fields in the model. If you have to specify some fields then replace __all__ with a Python list that specifies the fields that you want. Like this: fields = [“first_name”, “last_name”]. If you have to exclude some fields, create an exclude property with a list of fields to exclude from the model form. Like this: exclude = [“status”, “age”].

Creating Article & Category ModelForms

In part 7 of this complete beginners guide to Django, we showed you how to part 7: create views by creating the lits, detail, edit and delete views for articles and categories. In this series we will complete the create and edit views, and template code for creating and editing articles and categories.

Create a forms.py file in the django_project/blog/ app.

Copy and paste the following lines of code and save:

# import forms
from django import forms

# import the model you want to create a form from
from .models import Article, Category
 

class ArticleForm(forms.ModelForm):

  class Meta: 
    # specify the name of model to use and the fields

    model = Article
    fields = "__all__"


class CategoryForm(forms.ModelForm):

  class Meta:
    # specify the name of model to use and the fields

    model = Category
    fields = "__all__"

  • Notice that each form class begins with the name of the model which we are converting to form, then ends with Form.

Using ModelForms in Views

Let’s use our modelforms in the views!

Open the views.py file in the django_project/blog/ app, import ArticleForm and CategoryForm modelforms we created in the forms.py file:

from django.shortcuts import render, get_object_or_404, redirect

from blog.models import Article, Category
from .forms import ArticleForm, CategoryForm

Find the new_article views and update with the following code:

def new_article(request):
  article_form = ArticleForm()

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

    if article_form.is_valid():
      article = article_form.save(commit=False)
      article.author = request.user.author.user
      article.save()
      return redirect('homepage')
    else:
      article_form = ArticleForm(request.POST)
  else:
    article_form = ArticleForm()

  context = {
    'article_form' : article_form,
  }

  return render(request, 'blog/new-article.html', context)
  • article_form = ArticleForm() creates an (empty) instance of the ArticleForm modelform.
  • Next we check if the HTTP request method is equal to POST, then populate or fill the ArticleForm form with the values the user has typed using request.POST.
  • After that we check if the form is valid using the is_valid property which returns a boolean value of True or False. If the form is valid (True), then we set commit=False which means that do not save the form yet, but give me access to the form fields. This is very helpful when you want to access the id field, or you want to assign values to a field. In this case, we want to assign an author to the author field when somebody adds an article.
  • We assign the currently logged-in user as the author of the article using author = request.user.author.user. To avoid errors, be sure you are still logged-in to the Django admin site. If not then see part 5: working with Django admin site for more.
  • Once this is done, we save the article using the save() method using the Django ORM (see part 6: making queries (Django ORM) for more) and redirect to the homepage using the redirect() function (from django.shortcuts). Notice we passed homepage to the redirect function. Remember the URL name we gave to our homepage path in django_project/blog/urls.py? That is it in action!
  • Finally, we returned the form to the template through a context variable.

Note:

  • There are other HTTP request methods, POST and GET are popular.
  • It is possible to save a form without checking if it’s valid (is_valid). However, it is a very good practice to check if form is valid before saving to the database.

Update the new_category views with:

def new_category(request):
  category_form = CategoryForm()
  
  if request.method == 'POST':
    category_form = CategoryForm(request.POST)

    if category_form.is_valid():
      category_form.save()
      return redirect('homepage')
    else:
      category_form = CategoryForm(request.POST)
  else:
    category_form = CategoryForm()

  context = {
    'category_form' : category_form,
  }

  return render(request, 'blog/new-category.html', context)
  • The views for new_category is the same as that of new_article except that it doesn’t need the author field, so we didn’t set commit=False to assign a value to any field.

Updating New Article & Category Templates

We will update the new-article and new-category templates we created in the previous tutorial series – part 7: creating views.

Open the django_project/blog/templates/blog/new-article template and update with the following:

{% extends 'blog/base.html' %}
{% block title %}New Article{% endblock %}

{% block content %}

<h2>New Article</h2>
<form method="POST">
  {% csrf_token %}
  {{ new_article.as_p }}
  <input type="submit" value="Create Article">
</form>

{% endblock %}
  • Since we have a base template, we extend it using the extends keyword.
  • After that, we created a form with a method=”POST”. Remember we used if request.method == ‘POST’: in the new_article and new_category views? This is it!
  • Since web application vulnerability is a hot topic, Django as a Python web framework takes security to its heart by providing us with the best ways to build secure Django applications. {% csrf_token %} which means Cross Site Request Forgery Token or X Site Request Forgery Token is a security token provided by Django to be used in forms in order to avoid Cross Site Request Forgeries. For security reasons, if you forget to add the {% csrf_token %} token, then you will not be able to submit your forms for processing.
  • Remember article_form we passed from views to template in form of contexts? This is how Django creates a form by calling the form variable sent from views (context) to template, in double curly braces {{ article_form.as_p }}. as_p helps us display the form in a paragraph. That is; each field will be wrapped or put in a paragraph tag – p.
  • After that, we add a submit button to allow us send the form inputs to the server for processing. This submit button will send a POST request to the server because we set <form method=”POST”> which we checked using if request.method == ‘POST’:.
  • Finally we close our form tag (form).

Update the new-category template with the code below:

{% extends 'blog/base.html' %}
{% block title %}New Category{% endblock %}

{% block content %}

<h2>New Category</h2>
<form method="POST">
  {% csrf_token %}
  {{ new_category.as_p }}
  <input type="submit" value="Create Category">
</form>

{% endblock %}
  • The new-article template is the same as the new-category template.

Note:

  • Your button must be of type=”submit” which shows that it is a submit button not a regular button – type=”button”.
  • Your submit button must be inside of the
    tag.

Make sure your development server is running, if not then run the following command in the terminal:

python manage.py runserver

Visit the new articles’ page via 127.0.0.1:8000/new-article.

For new articles’ page, you should see:

Django Blog - New Articles

Go to the new categories’ page via 127.0.0.1:8000/new-category. This is what it looks like:

Django Blog - New Categories

If you notice, the article form as well as the category forms are not looking presentable. Let’s fix this!

Using Django Crispy Forms to Style Django Forms

To style a Django form, we will use the django-crispy-forms package which is a highly customizable Django package that comes with Bootstrap styles to customize forms.

Press ctrl + c to stop the Django development server. Use the following command to install django-crispy-forms using pip on the terminal:

pip install django-crispy-forms

Open django_project/settings.py and find the INSTALLED_APPS section:

# Application definition

INSTALLED_APPS = [
  'django.contrib.admin',
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.messages',
  'django.contrib.staticfiles',

  'blog',
]

Update it by adding crispy_forms to it:

# Application definition

INSTALLED_APPS = [
  'django.contrib.admin',
  'django.contrib.auth',
  'django.contrib.contenttypes',
  'django.contrib.sessions',
  'django.contrib.messages',
  'django.contrib.staticfiles',

  'crispy_forms',

  'blog',
]

After that scroll to the bottom of the settings.py file and add the following lines of code to it:

# CRISPY FORMS
CRISPY_TEMPLATE_PACK = 'bootstrap5'

bootstrap5 is the current version of Bootstrap. So, we will use Bootstrap to style our forms.

Finally open the new-article template and update with the following:

{% extends 'blog/base.html' %}
{% load crispy_forms_tags %}
{% block title %}New Article{% endblock %}

{% block content %}

<h2>New Article</h2>
<form method="POST">
  {% csrf_token %}
  {{ new_article|crispy }}
  <input type="submit" value="Create Article">
</form>

{% endblock %}

Update the new-category template with the following:

{% extends 'blog/base.html' %}
{% load crispy_forms_tags %}
{% block title %}New Category{% endblock %}

{% block content %}

<h2>New Category</h2>
<form method="POST">
  {% csrf_token %}
  {{ new_category|crispy }}
  <input type="submit" value="Create Category">
</form>

{% endblock %}

Run your development server if it is not running:

python manage.py runserver

Visit the new articles’ page again via 127.0.0.1:8000/new-article and create an article:

Django Blog - Create Articles

Goto the new categories’ page again via 127.0.0.1:8000/new-category and create an new category:

Django Blog - Create Articles

Updating Article & Category Views – Edit Views

In this section, we will update the article and category edit views to allow us edit articles and categories using the modelforms we created.

Update the edit_article views with the views function code below:

def edit_article(request, article_id):
  article = get_object_or_404(Article, id=article_id)

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

    if article_form.is_valid():
      article_form.save()
      return redirect('homepage')
    else:
      article_form = ArticleForm(request.POST)
  else:
    article_form = ArticleForm(instance=article)
  
  context = {
    'article' : article,
    'article_form' : article_form,
  }

  return render(request, 'blog/edit-article.html', context)

  • We retrieve the article that we want to edit, then use instance to pass it to the form. instance means populate or fill the form fields with the current content for editing.
  • Notice – we are not using commit=False because we are not editing any field before saving.

Update the edit_category views with the views function code below:

def edit_category(request, category_id):
  category = get_object_or_404(Category, id=category_id)

  if request.method == 'POST':
    category_form = CategoryForm(request.POST, instance=category)

    if category_form.is_valid():
      category_form.save()
      return redirect('homepage')
    else:
      category_form = CategoryForm(request.POST)
  else:
    category_form = CategoryForm(instance=category)
  
  context = {
    'category' : category,
    'category_form' : category_form,
  }

  return render(request, 'blog/edit-category.html', context)

Updating Edit Article & Category Templates

Open the edit-article template and update with the following lines:

{% extends 'blog/base.html' %}
{% load crispy_forms_tags %}
{% block title %}Editing {{ article.title }} (Article){% endblock %}

{% block content %}

<h2>Editing {{ article.title }}</h2>
<form method="POST">
  {% csrf_token %}
  {{ edit_article|crispy }}
  <input type="submit" value="Update Article">
</form>

{% endblock %}

Update the edit-category template with the following lines:

{% extends 'blog/base.html' %}
{% load crispy_forms_tags %}
{% block title %}Editing {{ category.title }} (Category){% endblock %}

{% block content %}

<h2>Editing {{ category.title }}</h2>
<form method="POST">
  {% csrf_token %}
  {{ edit_category|crispy }}
  <input type="submit" value="Update Category">
</form>

{% endblock %}

Adding Article & Category Edit Button to the Homepage & Detail Templates

Open the homepage template. Find the article for loop section:

<ul>
  {% for article in articles %}
  <li><a href="{{ article.get_absolute_url }}" title="{{ article.title }}">{{ article.title }}</a> - created on {{ article.created_at }} updated on {{ article.updated_at }} by {{ article.author.user.username }} category - {{ article.category.title }} | <a href="{{ article.get_delete_url }}">Delete</a></li>
  {% empty %}
  <li>There are no articles yet!</li>
  {% endfor %}
</ul>

Update with the following:

<ul>
  {% for article in articles %}
  <li><a href="{{ article.get_absolute_url }}" title="{{ article.title }}">{{ article.title }}</a> - created on {{ article.created_at }} updated on {{ article.updated_at }} by {{ article.author.user.username }} category - {{ article.category.title }} | <a href="{{ article.get_delete_url }}">Delete</a> | <a href="{{ article.get_edit_url }}">Edit</a></li>
  {% empty %}
  <li>There are no articles yet!</li>
  {% endfor %}
</ul>

Find the category for loop section:

<ul>
  {% for category in categories %}
  <li><a href="{{ category.get_absolute_url }}" title="{{ category.title }}">{{ category.title }}</a> - created on {{ category.created_at }} updated on {{ category.updated_at }} | <a href="{{ category.get_delete_url }}">Delete</a></li>
  {% empty %}
  <li>There are no categories yet!</li>
  {% endfor %}
</ul>

Update with the following:

<ul>
  {% for category in categories %}
  <li><a href="{{ category.get_absolute_url }}" title="{{ category.title }}">{{ category.title }}</a> - created on {{ category.created_at }} updated on {{ category.updated_at }} | <a href="{{ category.get_delete_url }}">Delete</a> | <a href="{{ category.get_edit_url }}">Edit</a></li>
  {% empty %}
  <li>There are no categories yet!</li>
  {% endfor %}
</ul>
  • Note – we had already created the get_delete_url as well as the get_edit_url model methods in the Article Model and in the Category Model. See part 4: creating models for more.

To add the edit button to the article detail page, open the article-detail.html template and find:

<p>Created on {{ article.created_at }} updated on {{ article.updated_at }} by {{ article.author.user.username }} category - {{ article.category.title }} | <a href="{{ article.get_delete_url }}">Delete</a></p>

Update with:

<p>Created on {{ article.created_at }} updated on {{ article.updated_at }} by {{ article.author.user.username }} category - {{ article.category.title }} | <a href="{{ article.get_delete_url }}">Delete</a> | <a href="{{ article.get_edit_url }}">Edit</a></p>

Open the category-detail.html template and find:

<p>Created on {{ category.created_at }} updated on {{ category.updated_at }} | <a href="{{ category.get_delete_url }}">Delete</a></p>

Update category-detail.html template with:

<p>Created on {{ category.created_at }} updated on {{ category.updated_at }} | <a href="{{ category.get_delete_url }}">Delete</a> | <a href="{{ category.get_edit_url }}">Edit</a></p>

Visit your development server via 127.0.0.1:8000/ and edit any article:

Django Blog - Edit Article

After that, go back to the homepage, select and edit any category:

Django Blog - Edit Category

Complete Beginners Guide to Django

In this complete beginners guide to Django, we have been able to cover part 1 – part 7. If you need to read them again, or have not read them yet, please see:

In the next complete beginners guide to Django, we will show you:

We hope this complete beginners guide to Django – creating modelforms (part 8) helped you learn how to create forms from models (modelforms). This is a complete beginners guide to Django, so if you haven’t read part 7, then see part 7: creating views or see part 1 – introduction to Django to get started.

See other of our Django tutorials for more.

You Might Also Like

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.