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
    exclude = [
      'author'
    ]


class CategoryForm(forms.ModelForm):

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

    model = Category
    exclude = [
      'author'
    ]

  • 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 blog.models import Article, Category
from .forms import ArticleForm, CategoryForm

Find the new_article views and update with the following code:

# new article view
def new_article(request):
  article_form = ArticleForm()

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

    if article_form.is_valid():
      article = article_form.save(commit=False)
      article.author = request.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. Then we use request.FILES to collect the media file from the form, which is an image.
  • 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:

# new category view
def new_category(request):
  category_form = CategoryForm()
  
  if request.method == 'POST':
    category_form = CategoryForm(request.POST)

    if category_form.is_valid():
      category = category_form.save(commit=False)
      category.author = request.user
      category.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 view 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 add the following two lines of code under the form element:

  {% csrf_token %}
  {{ article_form.as_p }}
  • 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).

Open the django_project/blog/templates/blog/new_category template and add the following two lines of code under the form element:

  {% csrf_token %}
  {{ category_form.as_p }}
  • 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 form 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

Once the installation is successfull, 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 class="text-center">New Article</h2>
<div class="row d-flex justify-content-center">
  <div class="col-6 mb-3">
    <div class="card p-3">

      <form method="POST" enctype="multipart/form-data">
        {% csrf_token %}
        {{ article_form|crispy }}
        <input type="submit" value="New article" class="btn btn-sm btn-primary w-100 mt-3">
      </form>

  <div class="mt-3">
        <a href="{% url 'homepage' %}" class="btn btn-outline-secondary w-100">Cancel</a>
      </div>
    </div>
  </div>
</div>
{% endblock %}
  • We are using enctype=”multipart/form-data” because we want to upload a media file. So, use this each time you want to upload a file.

Open the new_category template with the following:

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

{% block content %}
<h2 class="text-center">New Category</h2>
<div class="row d-flex justify-content-center">
  <div class="col-8 mb-3">
    <div class="card p-3">

      <form method="POST">
        {% csrf_token %}
        {{ category_form|crispy }}
        <input type="submit" value="New category" class="btn btn-sm btn-primary w-100 mt-3">
      </form>
      
      <div class="mt-3">
        <a href="{% url 'homepage' %}" class="btn btn-outline-secondary w-100">Cancel</a>
      </div>
    </div>
  </div>
</div>

{% endblock %}
  • Notice we are not using enctype=”multipart/form-data” because the new_category view function does not need any media upload

After that, open the settings.py file and add the following lines at the bottom:

# CRISPY FORMS
CRISPY_TEMPLATE_PACK = 'bootstrap4'

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 Article

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

Django Blog - Create Category

When you go to the homepage, blog or the article detail page, you will notice that the article feature image is not showing. This is because images are static files and they need to be configurated.

Configuring Static Files

To configure static files in Django, we will update settings.py and the project’s urls.py file.

Open settings.py, find STATIC_URL = ‘static/’. Add the following lines of code under it:

STATICFILES_DIRS = [
  os.path.join(BASE_DIR, 'static'),
]

MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
  • STATIC_URL will make your static files to be accessible via 127.0.0.1/static/logo.png.
  • STATICFILES_DIRS means where to find static files in your Django project. Here we have static which basically points to the static folder we created in the blog app.

After you have done that, open django_project/urls.py – root project URLS file. Add the following imports where path and include are:

from django.conf import settings
from django.conf.urls.static import static

Add the following lines of code at the bottom:

urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

Finally, create a django_project/static folder in your project root directory, where the blog app is and the manage.py file is.

Once you finish, delete the old article you added and add another one.

Go to the homepage blog or the article detailed page. 127.0.0.1:8000/.

Django Blog

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:

# edit article view
def edit_article(request, article_id):
  article = get_object_or_404(Article, id=article_id, author=request.user)

  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. Notice that we added author=request.user. This is to prevent someother perform from editing our own article.
  • 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:

# edit category view
def edit_category(request, category_id):
  category = get_object_or_404(Category, id=category_id, author=request.user)

  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 %}Edit Article{% endblock %}

{% block content %}
<h2 class="text-center">Edit Article</h2>
<div class="row">
  <div class="col-8 mb-3">
    <div class="card p-3">

      <form method="POST">
        {% csrf_token %}
        {{ article_form|crispy }}
        <input type="submit" value="Edit article" class="btn btn-sm btn-warning w-100">
      </form>
      
      <div class="mt-3">
        <a href="{% url 'homepage' %}" class="btn btn-outline-secondary w-100">Cancel</a>
      </div>
    </div>
  </div>
</div>

{% endblock %}

Update the edit_category template with the following lines:

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

{% block content %}
<h2 class="text-center">Edit Category</h2>
<div class="row">
  <div class="col-8 mb-3">
    <div class="card p-3">

      <form method="POST">
        {% csrf_token %}
        {{ category_form|crispy }}
        <input type="submit" value="Edit category" class="btn btn-sm btn-warning w-100">
      </form>
      
      <div class="mt-3">
        <a href="{% url 'homepage' %}" class="btn btn-outline-secondary w-100">Cancel</a>
      </div>
    </div>
  </div>
</div>

{% endblock %}

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

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

{% for article in articles %}

Update with the following:

{% for article in articles %}
  <div class="col-12 col-md-4 col-lg-3 mb-3">
    <div class="card">
      <img src="{{ article.featured_img.url }}" class="card-img-top" alt="{{ article.title }}">
      <div class="card-body">
        <h5 class="card-title"><a href="{{ article.get_absolute_url }}" title="{{ article.title }}">
            {{ article.title }}</a>
        </h5>
        <p class="card-text">{{ article.content|truncatewords:20 }}</p>
        <a href="{{ article.get_absolute_url }}" title="{{ article.title }}" class="btn btn-sm btn-primary">Read
          more</a>
        | <a href="{{ article.get_delete_url }}" title="{{ article.title }}" class="btn btn-sm btn-danger">Delete</a>
        | <a href="{{ article.get_edit_url }}" class="btn btn-sm btn-warning">Edit</a>
      </div>
    </div>
  </div>
{% empty %}
<p>There are no articles yet!</p>
{% endfor %}

Find the category for loop section:

  {% for category in categories %}

Update with the following:

  {% for category in categories %}
  <li class="mb-3"><a href="{{ category.get_absolute_url }}" title="{{ category.title }}">{{ category.title }}</a> | <a
      href="{{ category.get_delete_url }}" title="{{ category.title}}" class="btn btn-sm btn-danger">Delete</a>
      | <a href="{{ category.get_edit_url }}" class="btn btn-sm btn-warning">Edit</a>
  </li>
  • 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 </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 }}"  class="btn btn-sm btn-danger">Delete</a> | <a href="{{ article.get_edit_url }}" class="btn btn-sm btn-warning">Edit</a></p>

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

<h2 class="text-center">Posts in {{ category.title }}</h2>

Update with:

<h2 class="text-center">Posts in {{ category.title }} <a href="{{ category.get_edit_url }}" class="btn btn-sm btn-warning">Edit</a></h2>

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.

Shares