Getting Started with Django, Part 2: Using Forms

In Part 1 of this series, we learned about the Django MTV model, and how to set up a basic Django Todo list web application. In this article, let’s put your new Django skills to use by applying CRUD (Create, Read, Update, and Delete) operations to your Todo list app.

Django forms

To set up forms in Django, create a new file tasks/forms.py and add the following:

from django import forms
from .models import Task


class TaskForm(forms.ModelForm):
    class Meta:
        model = Task
        fields = "__all__"

To render that form in the root URL, edit the index view method in the tasks/views.py file:

from django.shortcuts import render
from .models import Task
from .forms import TaskForm


def index(request):
    tasks = Task.objects.all()
    form = TaskForm()
    context = {'tasks': tasks, 'form': form}
    return render(request, 'tasks/list.html', context)

The view passes the context to the template. Let’s edit the list template in tasks/templates/tasks/list.html:

<h1>To Do</h1>
<form>
    {{ form.text }}
    <input type="Submit" value="Add task">
</form>
{% for task in tasks %}
<div>
    <ul>
        <li>{{ task }}</li>
    </ul>
</div>
{% endfor %}

The new form is now created:

add form to django app

Creating an item

Now, the form is not ready yet to accept user input. We want to click on the Add task button and insert the new todo item to the list.

You can start with the list template:

{% csrf_token %}

Here, we configure the method attribute in the HTML form tag to be a POST request. However, the action attribute has the endpoint that the form should send the data when the form is submitted.

We wrap the form with the csrf_token, which provides CSRF protection. This is done to prevent attacks from hackers who can use form data with unauthorized commands.

Now, the form redirects to the root endpoint but without submitting the task to the database. Let’s save the todo item to the database; moving forward, it will be listed in the tasks bullet points.

tasks/views.py:
from django.shortcuts import redirect


def index(request):
    tasks = Task.objects.all()
    form = TaskForm()
    if request.method == 'POST':
        form = TaskForm(request.POST)
        if form.is_valid():
            form.save()
        return redirect("/")
    context = {'tasks': tasks, 'form': form}
    return render(request, 'tasks/list.html', context)

Here, we check that the request method coming is a POST request. We assign the form to the new task form and we check whether the form is valid or not. Finally, we save the form to the database. If the form is not valid, there will be a redirect to the root URL.

Updating an item

So far, we’ve seen the R (Read) and C (Create) from the CRUD operations. Let’s see the U (Update) operation and how to update todo items in our app.

Create a new template tasks/templates/tasks/update.html:

<h1>Update</h1>
<form method="POST">
    {% csrf_token %}
    {{ form }}
    <input type="Submit" value="Update">
</form>

This template has a form similar to the list template except that the action attribute is omitted from the form tag. This shows that the form-data will be sent to the current page.

Another difference from the form in the list template is that it has the whole form data attributes {{ form }}, not just the text of the task form. Now, we can edit the boolean value of the is_done attribute.

To pass data to that template, you need to create a view method to the update operation in tasks/views.py:

from django.shortcuts import redirect


def update(request, id):
    task = Task.objects.get(id=id)
    form = TaskForm(instance=task)
    if request.method == 'POST':
        form = TaskForm(request.POST, instance=task)
        if form.is_valid():
            form.save()
        return redirect("/")
    context = {'form': form}
    return render(request, 'tasks/update.html', context)

This function is similar to the index function except that:

  • It takes another parameter id to refer to the selected todo item.
  • It has the form related to the selected task instance.

To route the new update URL, you need to add a new URL pattern in tasks/urls.py:

urlpatterns = [
    path('', views.index),
    path('update/', views.update, name='update'),
    ]

The update URL is followed by the path argument (id of the task). This endpoint is called with update name. We will call this endpoint in the tasks/templates/tasks/list.html template inside the for loop after each task:

...
{% for task in tasks %}
<div>
    <ul>
        <li>{{task}}</li>
        <a href="{% url 'update' task.id %}">Update</a>
    </ul>
</div>
{% endfor %}

Now, let’s experiment with the last change. Hit refresh to the localhost so that you can find an Update button after each task.

You can add an item like “Write”:

Click on the Update button. You’ll get redirected to this page:

update in action

Here’s the output when it’s edited to “Play” after being redirected to the root URL:

update item on django todo list

Deleting an item

To delete an item, let’s create a new template tasks/templates/tasks/delete.html:

<h1>Delete</h1>
<p>Are you sure you want to delete {{task}}?</p>
<a href="{% url 'list' %}">Cancel</a>
<form method="POST">
    {% csrf_token %}
    <input type="Submit" value="Confirm">
</form>

Here, we ask the user to confirm his action whether to delete the selected task or not. We give them two options: to cancel (and, in this case, there will be a redirect to the list URL) or to confirm (through a POST request to a form).

To render this template, you should create a view method for the delete operation in tasks/views.py:

def delete(request, id):
    task = Task.objects.get(id=id)
    if request.method == 'POST':
        task.delete()
        return redirect("/")
    context = {'task': task}
    return render(request, 'tasks/delete.html', context)

Here, we get the task and then check if the request is POST. We then delete this task from the database. Once it’s deleted, there’s a redirect to the root URL. Finally, we render the delete template with the context that only contains the task that is printed on the confirmation message.

What we need to do next is to update the URL patterns to have the delete endpoint in tasks/urls.py:

urlpatterns = [
    path('', views.index, name='list'),
    path('update/', views.update, name='update'),
    path('delete/', views.delete, name='delete')
    ]

Finally, you need to have the link of the Delete button after each task.

tasks/templates/tasks/list.html:
...
{% for task in tasks%}
    <div>
        <p>{{task}}</p>
        <a href="{% url 'update' task.id %}">Update</a>
        <a href="{% url 'delete' task.id %}">Delete</a>
    </div>
{% endfor %}

Let’s experiment and look at the new index page which now has the delete buttons:

new index page with delete buttons

You can click on the Delete button on the Play task, for example. You’ll have a confirmation page like this:

Delete task from todo list

Crossing out completed items

If you want to cross out the completed items, you can do that with the del HTML tag.

tasks/templates/tasks/list.html:
{% for task in tasks %}
<div>
    <ul>
        {% if task.is_done %}
        <li><del>{{task}}</del></li>
        {% else %}
        <li>{{task}}</li>
        {% endif %}
        <a href="{% url 'update' task.id %}">Update</a>
        <a href="{% url 'delete' task.id %}">Delete</a>
    </ul>
</div>
{% endfor %}

Here, we check whether the task is done or not. If it’s done, it will be crossed out with the del tag. If it’s not done yet, it will be shown as before (bullet point, not crossed out).

Let’s try it out.

First, mark a task as done by updating its value. In our example, you’ve already read the tutorial:

mark task as done

After you’re being redirected to the index page, you’ll find the updated list with the crossed-out item:

todo list updated with crossed-out task

Wrapping up

In this tutorial, you demonstrated your Django skills by applying CRUD (Create, Read, Update, and Delete) operations on a simple Todo list web application that’s useful to get started with Django.

If you’re interested in playing around with this project, all this code is in this GitHub repo.

Want to learn more about Django? Here are some tutorials to check out:

Read more about:

django

Ezz is an AWS Certified Machine Learning Specialist and a Data Platform Engineer. Check out his website for more.