Python Django Projects | Create a Todo Web Application

In this tutorial, you’ll learn to create Todo Web Application using Python Django Framework.

After you complete this tutorial you’ll acquire below knowledge when working with Django.

  • Configuring MySql database.
  • Specifying template paths properly.
  • Basics of Template inheritance.
  • Creating views using class-based views.
  • Create a model form using customizing field widgets and properties
  • Displaying a flash message on operation success or failure using messages module.

Create Virtual Environment

Install environment using Python PIP package manager.

pip install virtualenv

Confirm installation by outputting virtualenv version.

virtualenv --version

Below command creates a virtual environment from your project. The env is simply a folder name. You can give it any name you like.

virtualenv env

For a more in-depth understanding of the virtual environment, you can visit this guide.

Now you must activate that env source using below command.

source env/bin/activate

We’ll be using MySql database for storing tasks so install mysqlclient for Django application.

(env) root@root-X541UJ:~/python$ pip install mysqlclient

Create a Django Project

After activating env source the file you will see (env) on terminal like this (env) root@root-X541UJ:~/python/.

Only after this step, you must create a new Django project using below command.

(env) root@root-X541UJ:~/python$ django-admin startproject todo

For creating a new Django app.

(env) root@root-X541UJ:~/python$ django-admin startapp todo_maker

Configure Setting.py file

In todo/settings.py

# Register app
INSTALLED_APPS = [
    ...
    'todo_maker.apps.TodoMakerConfig',
]

# Connect to MySQL database
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django_todo',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': 'localhost',   # Or an IP Address that your DB is hosted on
        'PORT': '3306',
        'OPTIONS': {
            'sql_mode': 'traditional',
        }
    }
}

# Make sure django finds all the templates in TEMPLATES list
'DIRS': [
    os.path.join(BASE_DIR, 'todo/templates/todo'),
    os.path.join(BASE_DIR, 'templates'),
],

Include the todo_maker app in todo/urls.py.

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('', include('todo_maker.urls')),
    path('admin/', admin.site.urls),
]    

Setup base template for inheritance

In todo/templates/todo/base.html create base.html.

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Django Todo Application</title>

    {% block stylesheets %}

    {% endblock  %}

</head>
<body>
    {% block content %}
        
    {% endblock  %}
        
</body>
</html>

Making Models for Todo App

In todo_maker/models.py.

from django.db import models
from datetime import datetime

# Create your models here.
class Task(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=50)
    description = models.TextField(null=True)
    task_accomplished = models.IntegerField(default=0)
    created_at = models.DateTimeField(auto_now_add=datetime.now(), null=True)
    updated_at = models.DateTimeField(auto_now=datetime.now(), null=True)

    class Meta:
        db_table = "tasks"
        verbose_name = "Task"
        verbose_name_plural = "Tasks"

    def __str__(self):
        return "ID = {}, title = {}".format(self.description, self.title)

The model Task is used to store task information given by the user. The task_accomplished is an integer field which takes two values 1 means task completed or 0 means task pending.

Now create a migration file for the Task model.

(env) root@root-X541UJ:~/python$ python manage.py makemigrations todo_maker

Use below command to create task table in the database.

(env) root@root-X541UJ:~/python$ python manage.py migrate

Preview of Database Tables

Python Django Projects - Database Tables Preview

Add routes for listing, creation, deletion and task completion of a task

In todo_maker/urls.py.

from django.urls import path
from todo_maker import views
app_name = "todo_maker"

urlpatterns = [
    path('', views.List.as_view(),name="list-todo"),
    path('save', views.List.as_view(),name="save-todo"),
    path('edit/', views.List.as_view(),name="edit-todo"),
    path('remove/', views.List.as_view(),name="remove-todo"),
    path('task-completed', views.List.as_view(),name="todo-task-completed"),
] 

Create ModelForm to save and update the task object

In todo_maker/forms.py.

from django.forms import Form, ModelForm
from django import forms
from todo_maker.models import Task
from django.utils.safestring import mark_safe

class TaskCreateForm(ModelForm):
    id = forms.CharField(required=False)
    task_accomplished = forms.ChoiceField(
            widget = forms.Select(attrs={'class': "form-control"}),
            choices=[(0, "Task Pending"), (1, "Task Accomplished")],
        )
    class Meta:
        model = Task
        fields = ("id", "title", "description", "task_accomplished",)
        labels = {
            "title" : mark_safe("Task Title {}".format("<span class='required' >*</span>")),
            "task_accomplished" : mark_safe("Task accomplished {}".format("<span class='required' >*</span>")),
        }
        widgets = {
            "title" : forms.TextInput(attrs={'class' : 'form-control'}),
            "description" : forms.Textarea(attrs={'class' : 'form-control', 'rows' : 2}),
        }

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

Here TaskCreateForm uses ModelForm and the referred model is Task a model. Here title, task_accomplished fields are mandatory field.

Prepare Views for Todo App

In todo_maker/views.py.

from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.views import View
from todo_maker.models import Task
from todo_maker.forms import TaskCreateForm
from django.contrib import messages
from django.urls import reverse

# Create your views here.
class List(View):
    template = "todo_maker/list.html"

    def get(self, request, *args, **kwargs):
        instance = None
        if kwargs.get('pk', None):
            try:
                instance = Task.objects.filter(pk=kwargs.get('pk', None))[0]
            except Exception:
                instance = None

        delete_id = kwargs.get('delete_id', None)
        
        if delete_id:
            try:
                obj = Task.objects.filter(pk=delete_id)[0]
                obj.delete()
                messages.add_message(request, messages.SUCCESS, "Task removed successfully.")
            except Exception:
                pass
                
        form = TaskCreateForm(instance=instance)
        context = {
            "tasks" : Task.objects.filter().order_by('task_accomplished'),
            "form" : form,
        }

        return render(request, self.template, context)
    
    def post(self, request, *args, **kwargs):
        instance = None
        post  = request.POST.copy()
        pk = post.get('id', None)
        
        msg = "New Task Created..."
        if pk:
            try:
                msg = "Task Updated..."
                instance = Task.objects.filter(pk=pk)[0]
            except Exception:
                instance = None

        form = TaskCreateForm(post, instance=instance)

        if form.is_valid():
            instance = form.save(commit=False)
            instance.save()
            messages.add_message(request, messages.SUCCESS, msg)
        else:
            messages.add_message(request, messages.ERROR, "Please fill * marked fields")
            
        url = reverse("todo_maker:list-todo")
        return redirect(url)

In form = TaskCreateForm(instance=instance) the instance is the task object that is used for populating TaskCreateForm.
Here get() method of ListView performs two functions they are :

  • It displays the list of tasks in the first task pending order.
  • If pk value is passed then it populates the form with task data of given task id.

Also, the post() method of ListView performs two functions of saving and updating form data passed to TaskCreateForm.

Create Templates

In todo_maker/templates/todo_maker/list.html.

{% extends "base.html" %}

{% block stylesheets %}
{{ block.super }}

<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">

<style>
body{
    margin: 0;
    padding: 0;
    background-color: #009688;
}
.container-fluid > .wrapper{
    width: 50%;
    margin: auto;
    padding-top: 50px;
    padding-bottom: 50px;
    background-color: #ffffff;
}

.title-container{
    background-color: aliceblue;
    padding: 10px 0;
}

.title-container h1{
    margin: 0;
    text-align: center;
}

.create-task-container, .list-task-container{
    padding: 15px 25px 15px 25px;
}

.message-container{
    margin-top: 15px;
}

.required{
    color: red;
}
</style>

{% endblock  %}

{% block content %}
<div class="container-fluid">
    <div class="wrapper">

        <div class="title-container">
            <h1>Django Todo Application</h1>
        </div>

        {% if messages %}
        <div class="message-container">
            {% for message in messages %}
                {% if message.level == DEFAULT_MESSAGE_LEVELS.SUCCESS %}
                    <div class="alert border-0 alert-success mar-bot-10-imp alert-dismissible fade show border-radius-none" role="alert">
                        <div id="success-notification-div">
                            {{ message }}
                        </div>

                        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                            <i class="ti ti-close"></i>
                        </button>
                    </div>
                {% endif %}

                {% if message.level == DEFAULT_MESSAGE_LEVELS.ERROR %}
                    <div class="alert border-0 alert-danger mar-bot-10-imp alert-dismissible fade show border-radius-none" role="alert">
                        <div id="danger-notification-div">
                            {{ message }}
                        </div>
                                        
                        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                            <i class="ti ti-close"></i>
                        </button>
                    </div>
                {% endif %}
            {% endfor %}
        </div>
        {% endif %}

        <div class="create-task-container">
            <h2 class="">Create Task</h2>
            <form action="{% url 'todo_maker:save-todo' %}" method="post">
                {% csrf_token %}
                {{ form.id.as_hidden }}
                <div class="form-group">
                    {{form.title.label}}
                    {{form.title}}
                </div>
                
                <div class="form-group">
                    {{form.description.label}}
                    {{form.description}}
                </div>

                <div class="form-group">
                    {{form.task_accomplished.label}}
                    {{form.task_accomplished}}
                </div>

                <div class="form-group">
                    <input type="submit" class="btn btn-primary" value="Save">
                </div>
            </form>
        </div>
    
        <div class="list-task-container">
            <h2 class="">List Tasks</h2>
            <table border="1" class="task-table table table-bordered"  >
                <thead>
                    <tr>
                        <td>Title</td>
                        <td>Description</td>
                        <td>Action</td>
                    </tr>
                </thead>
                <tbody>
                    {% if tasks %}
                        {% for task in tasks %}
                        <tr>
                            <td>
                                {{ task.title }} 
                                {% if task.task_accomplished == 1 %}
                                    <span class="badge badge-success">Task Completed</span>
                                {% endif %}
                            </td>
                            <td>{{ task.description }}</td>
                            <td>
                                <a href="{% url 'todo_maker:edit-todo' pk=task.pk %}" class="btn btn-info btn-sm">Edit</a>
                                <a href="{% url 'todo_maker:remove-todo' delete_id=task.pk %}" class="btn btn-danger btn-sm">Remove</a>
                            </td>
                        </tr>
                        {% endfor %}
                    {% else %}
                    <tr>
                        <td colspan="3">No task available</td>
                    </tr>
                    {% endif %}
                </tbody>
            </table>
        </div>
    </div>
</div>
{% endblock  %}

For the remainder, base.html is imported from a folder todo/templates/src/.

Run Django Todo Application

(env) root@root-X541UJ:~/python/todo$ python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
May 08, 2020 - 13:21:44
Django version 2.2.7, using settings 'todo.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Todo Application Final Output

Python Django Projects - Todo Web Application Final Output

Video Preview

Conclusion

In conclusion, we have come to an end. For more information on Django comment below and if you would like to explore more about Django then you can visit Official Django Website.

Summary
Review Date
Reviewed Item
Python Django Projects | Create a Todo Web Application
Author Rating
51star1star1star1star1star
Software Name
Django Python Framework
Software Name
Windows Os, Mac Os, Ubuntu Os
Software Category
Web Development