Task Management System using Django

Django is a very robust full-stack web framework. It uses Python on the server side and provides API to integrate with almost any front-end web frameworks like React, Next, Vue, and Angular. In Django developers use MVT(Model View Template) architecture to build an application. In this project on Task Management Systems using Django, we are going to stick with built-in Django templates that use the Django Template Language. This project is a complete demonstration of Task Management Systems where the user can create their account and log in to the application. In the tasks section user can create a task with the rich text editor. From the home page, the user is capable of viewing, editing, and deletion a particular task.

Project Overview

Technologies and Skills Required to Implement this project

  1. Python
  2. Django web framework
  3. SQLite
  4. Bootstrap
  5. HTML
  6. CSS
  7. JavaScript

Building Django task management system

This project will have limited functionalities of a regular task management system software or application. The user can create an account and log in to the application with proper form validation of each field. After login, the user will be automatically redirected to the home or all tasks section of the application containing four columns To Do, Started, Complete, and Archive. Initially, tasks will be empty and the user can add new tasks with the task details. The project also includes a strict authentication system as all the routes are protected thus unauthorized users can not access any page and will be automatically been redirected to the login page.

ER Diagram for Django Task Management System

User interface use cases

  1. The user creates a new account
  2. User login to an extent account
  3. Add new task
  4. Update existing task
  5. View task details
  6. Delete task
  7. Rich Text Editor support for user

Admin interface use cases

  1. Create new account
  2. Login as administrator
  3. Admin can perform CRUD operation on all the data
  4. Manage user details
  5. Manage user permissions

System requirements

  1. The system must have an updated version of Python and PIP installed. Here we are using Python 3.11.7 and PIP 23.3.2.
  2. One pip package we need to run this project smoothly is virtualenv 20.25.0.
  3. We are using VS Code Editor but it's not the only one but rather the preferred one.

Steps to implement this project

  • Create a new directory named django_task_management_system.
  • Open any terminal in the directory django_task_management_system and run this command python -m venv venv to create a new virtual environment specifically for this project. We can also run the application without this step but it's not recommended to do so as per the Django Official Documentation.
  • Now run code .  to open the directory in VS Code. Till now if everything is executed properly then the output must be like this.

  • To activate the virtual environment run venv/Scripts/activate , this step might ask for some permission, or the operating system might find it a threat but we have to allow it.

  • Execute `pip install django, django-ckeditor`, this command will install Django and ckeditor in the project environment.
  • Run `django-admin startproject django_task_management_system`, it will create a new Django application in the working directory of the terminal.

  • Move the manage.py  file and the django_task_management_system directory to the parent directory and the folder structure should be like in the image.
  • Execute python manage.py startapp base in the terminal, it will create an app with the name base.
  • Run python manage.py runserver , this will start a local server at localhost:8000 and will display a Django start page like this. Some errors might occur in the terminal but those are nothing to worry about.

  • Turn off the running local server by pressing ctrl + c and let's start creating the application.
  • Open the settings.py file in the django_task_management_system directory and replace the default code with this code.

		# django_task_management_system/settings.py
            """
            Django settings for django_task_management_system project.
            
            Generated by 'django-admin startproject' using Django 5.0.
            
            For more information on this file, see
            https://docs.djangoproject.com/en/5.0/topics/settings/
            
            For the full list of settings and their values, see
            https://docs.djangoproject.com/en/5.0/ref/settings/
            """
            
            from pathlib import Path
            
            # Build paths inside the project like this: BASE_DIR / 'subdir'.
            BASE_DIR = Path(__file__).resolve().parent.parent
            
            # SECURITY WARNING: keep the secret key used in production secret!
            SECRET_KEY = 'django-insecure-#kfz(&+ra1by8r1fbqlg=z$3w+@a$iq&89&0w*+(o81@&=n#%p'
            
            # SECURITY WARNING: don't run with debug turned on in production!
            DEBUG = True
            
            ALLOWED_HOSTS = []
            
            # Application definition
            
            INSTALLED_APPS = [
                'django.contrib.admin',
                'django.contrib.auth',
                'django.contrib.contenttypes',
                'django.contrib.sessions',
                'django.contrib.messages',
                'django.contrib.staticfiles',
            ]
            
            # Custom apps
            # These are the apps that we have added to our project
            CUSTOM_APPS = [
                'base.apps.BaseConfig',
                'ckeditor',
            ]
            
            # Add custom apps to the list of installed apps
            # This line adds our custom apps to the list of installed apps
            INSTALLED_APPS += CUSTOM_APPS
            
            MIDDLEWARE = [
                'django.middleware.security.SecurityMiddleware',
                'django.contrib.sessions.middleware.SessionMiddleware',
                'django.middleware.common.CommonMiddleware',
                'django.middleware.csrf.CsrfViewMiddleware',
                'django.contrib.auth.middleware.AuthenticationMiddleware',
                'django.contrib.messages.middleware.MessageMiddleware',
                'django.middleware.clickjacking.XFrameOptionsMiddleware',
            ]
            
            ROOT_URLCONF = 'django_task_management_system.urls'
            
            TEMPLATES = [
                {
                    'BACKEND': 'django.template.backends.django.DjangoTemplates',
                    'DIRS': [],
                    'APP_DIRS': True,
                    'OPTIONS': {
                        'context_processors': [
                            'django.template.context_processors.debug',
                            'django.template.context_processors.request',
                            'django.contrib.auth.context_processors.auth',
                            'django.contrib.messages.context_processors.messages',
                        ],
                    },
                },
            ]
            
            WSGI_APPLICATION = 'django_task_management_system.wsgi.application'
            
            # Database
            # https://docs.djangoproject.com/en/5.0/ref/settings/#databases
            
            DATABASES = {
                'default': {
                    'ENGINE': 'django.db.backends.sqlite3',
                    'NAME': BASE_DIR / 'db.sqlite3',
                }
            }
            
            # Password validation
            # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
            
            AUTH_PASSWORD_VALIDATORS = [
                {
                    'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
                },
                {
                    'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
                },
                {
                    'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
                },
                {
                    'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
                },
            ]
            
            # Internationalization
            # https://docs.djangoproject.com/en/5.0/topics/i18n/
            
            LANGUAGE_CODE = 'en-us'
            
            TIME_ZONE = 'UTC'
            
            USE_I18N = True
            
            USE_TZ = True
            
            # Login url
            # This is the URL where our login view is located
            LOGIN_URL = 'login'
            
            # Static files (CSS, JavaScript, Images)
            # https://docs.djangoproject.com/en/5.0/howto/static-files/
            
            STATIC_URL = 'static/'
            
            # Default primary key field type
            # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
            
            DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
  
	
  • Place this code in the django_task_management_system/urls.py file.

		# django_task_management_system/urls.py

            # Import the admin module from django.contrib
            from django.contrib import admin
            # Import the include and path functions from django.urls
            from django.urls import include, path
            
            # Define the URL patterns for this module
            urlpatterns = [
                # When the 'admin/' URL is requested, Django will serve the built-in admin site
                path('admin/', admin.site.urls),
                # When the root URL ('') is requested, Django will look for URLs in 'base.urls'
                path('', include('base.urls')),
            ]
    
	
  • Create a file name urls.py in the base directory and place this code in that file.
		# base/urls.py
        
            from django.urls import path
            # Import the views from the current directory's views.py
            from .views import TaskListView, TaskDetailView, TaskCreateView, TaskUpdateView, TaskDeleteView, CustomLoginView, RegisterPage
            from django.contrib.auth.views import LogoutView
  
            # Define the URL patterns for this module or application
            urlpatterns = [
                path('login/', CustomLoginView.as_view(), name='login'),
                path('register/', RegisterPage.as_view(), name='register'),
                path('logout/', LogoutView.as_view(next_page='login'), name='logout'),
                path('', TaskListView.as_view(), name='tasks'),
                path('task/<int:pk>/', TaskDetailView.as_view(), name='task'),
                path('add-new/', TaskCreateView.as_view(), name='add-new'),
                path('task-update/<int:pk>/', TaskUpdateView.as_view(), name='task-update'),
                path('task-delete/<int:pk>/', TaskDeleteView.as_view(), name='task-delete'),
            ]
    	
						
					
  • Place this code in the base/views.py file.
        # base/views.py
            # Import necessary modules from Django
from django.shortcuts import redirect
from django.views.generic import ListView, DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
from django.urls import reverse_lazy
from django import forms
from ckeditor.widgets import CKEditorWidget
from django.contrib.auth.models import User
from django.contrib.auth.views import LoginView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth import login
from django.contrib.auth.forms import UserCreationForm

# Import Task model from the current directory
from .models import Task

# Custom login view
class CustomLoginView(LoginView):
    template_name = 'base/login.html'  # Use custom login template
    fields = '__all__'
    redirect_authenticated_user = True  # Redirect user if already authenticated

    # Redirect to tasks page after successful login
    def get_success_url(self):
        return reverse_lazy('tasks')

# Custom user creation form with additional fields
class CustomUserCreationForm(UserCreationForm):
    email = forms.EmailField(required=True)
    first_name = forms.CharField(required=True)
    last_name = forms.CharField(required=True)

    class Meta:
        model = User
        fields = ("username", "first_name", "last_name", "email", "password1", "password2")  # Include additional fields

    # Save the additional fields to the user model
    def save(self, commit=True):
        user = super(CustomUserCreationForm, self).save(commit=False)
        user.email = self.cleaned_data['email']
        user.first_name = self.cleaned_data['first_name']
        user.last_name = self.cleaned_data['last_name']
        if commit:
            user.save()
        return user

# Registration page view
class RegisterPage(FormView):
    template_name = 'base/register.html'  # Use custom registration template
    form_class = CustomUserCreationForm  # Use custom user creation form
    redirect_authenticated_user = True  # Redirect user if already authenticated
    success_url = reverse_lazy('tasks')  # Redirect to tasks page after successful registration

    # Log in the user after successful registration
    def form_valid(self, form):
        user = form.save()
        if user is not None:
            login(self.request, user)
        return super(RegisterPage, self).form_valid(form)

    # Redirect to tasks page if user is already authenticated
    def get(self, *args, **kwargs):
        if self.request.user.is_authenticated:
            return redirect('tasks')
        return super(RegisterPage, self).get(*args, **kwargs)

# Task list view
class TaskListView(LoginRequiredMixin, ListView):
    model = Task
    context_object_name = 'tasks'  # Use 'tasks' as context object name

    # Filter tasks by user and search input
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['tasks'] = context['tasks'].filter(user=self.request.user)  # Filter tasks by user
        search_input = self.request.GET.get('search') or ''
        if search_input:
            context['tasks'] = context['tasks'].filter(title__icontains=search_input)  # Filter tasks by search input
        context['search_input'] = search_input

        # Add the count for each status type
        context['todo_count'] = context['tasks'].filter(status='todo').count()
        context['started_count'] = context['tasks'].filter(status='started').count()
        context['completed_count'] = context['tasks'].filter(status='complete').count()
        context['archived_count'] = context['tasks'].filter(status='archived').count()

        return context

# Task detail view
class TaskDetailView(LoginRequiredMixin, DetailView):
    model = Task
    context_object_name = 'task'  # Use 'task' as context object name

# Task creation view
class TaskCreateView(LoginRequiredMixin, CreateView):
    model = Task
    template_name = 'base/task_create.html'  # Use custom task creation template
    success_url = reverse_lazy('tasks')  # Redirect to tasks page after successful task creation
    form_class = forms.modelform_factory(model, fields=['title', 'description', 'status', 'due'],
                                         widgets={'due': forms.DateTimeInput(attrs={'type': 'datetime-local'})})  # Use custom form

    # Set the task's user to the current user
    def form_valid(self, form):
        form.instance.user = self.request.user
        return super(TaskCreateView, self).form_valid(form)

# Task update view
class TaskUpdateView(LoginRequiredMixin, UpdateView):
    model = Task  # Use Task model
    template_name = 'base/task_update.html'  # Use custom task update template
    success_url = reverse_lazy('tasks')  # Redirect to tasks page after successful task update
    # Use a form with 'title', 'description', 'status', 'due' fields and custom widgets for 'due' and 'description' fields
    form_class = forms.modelform_factory(Task, fields=['title', 'description', 'status', 'due'],
                                         widgets={'due': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
                                                  'description': CKEditorWidget()})

# Task deletion view
class TaskDeleteView(LoginRequiredMixin, DeleteView):
    model = Task  # Use Task model
    context_object_name = 'task'  # Use 'task' as context object name
    template_name = 'base/task_delete.html'  # Use custom task deletion template
    success_url = reverse_lazy('tasks')  # Redirect to tasks page after successful task deletion
    
    
  • Place this code in the base/models.py file.
        # base/models.py

            from django.db import models
            from django.contrib.auth.models import User
            from ckeditor.fields import RichTextField
            
            # Define choices for task status
            TASK_STATUS_CHOICES =  (
                ('todo', 'To Do'),
                ('started', 'Started'),
                ('complete', 'Complete'),
                ('archived', 'Archived'),
            )
            
            # Define Task model
            class Task(models.Model):
                user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
                title = models.CharField(max_length=200)
                description = RichTextField(blank=True, null=True) # RichTextField is a WYSIWYG editor
                status = models.CharField(max_length=20, choices=TASK_STATUS_CHOICES, default='todo')
                due = models.DateTimeField(null=True, blank=True)
                created = models.DateTimeField(auto_now_add=True)
                updated = models.DateTimeField(auto_now=True)
            
                # String representation of the task
                def __str__(self):
                    return self.title  # Use the task's title as its string representation
            
                # Meta data for the Task model
                class Meta:
                    ordering = ['-created']  # Order tasks by the 'created' field in descending order
    
  • Place this code in the **admin.py** file of the **base** directory of the application.
        # base/admin.py

            from django.contrib import admin
            from .models import Task
            
            # Register the Task model with the admin site
            admin.site.register(Task)
    
  • - Create a directory name **templates** in the **base** directory and add a file named **base.html** then place this code in the **base.html** file.
        <!-- base/templates/base.html -->

            <!DOCTYPE html>
            <html>
                <head>
                    <!-- Meta tags for character encoding, compatibility, viewport settings -->
                    <meta charset="utf-8">
                    <meta http-equiv="X-UA-Compatible" content="IE=edge">
                    <meta name="viewport" content="width=device-width, initial-scale=1">
            
                    <!-- Title of the page, can be overridden in child templates -->
                    <title>DTMS - {% block title %}{% endblock %}</title>
            
                    <!-- Link to Bootstrap CSS and JS, Google Fonts, Font Awesome and custom favicon -->
                    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
                    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>
                    <link href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
                    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
                    <link rel="icon" href="https://media.geeksforgeeks.org/gfg-gg-logo.svg">
                </head>
            
                <!-- Custom styles for body, form elements, and CKEditor -->
                <style>
                    body {
                        font-family: 'Raleway', sans-serif;
                    }
            
                    /* Style all form elements */
                    form {
                        margin: 20px 0;
                    }
            
                    /* Style all input fields, select dropdowns, and textareas */
                    form input,
                    form select,
                    form textarea {
                        width: 100%;
                        padding: 10px;
                        margin: 10px 0;
                        border: 1px solid #ccc;
                        border-radius: 4px;
                    }
            
                    /* Style submit buttons */
                    form input[type="submit"]:not(.no-global-style) {
                        background-color: #4CAF50 !important;
                        color: white;
                        padding: 10px 20px;
                        border: none;
                        border-radius: 4px;
                        cursor: pointer;
                        margin: 10px 0;
                    }
            
                    /* Add a hover effect for submit buttons */
                    form input[type="submit"]:not(.no-global-style):hover {
                        background-color: #0d6efd;
                    }
            
            
                    /* CKEditor styles */
                    .django-ckeditor-widget{
                        width: 100%;
                    }
            
                </style>
            
                <body>
                    <!-- Navigation bar -->
                    <nav class="navbar navbar-expand-lg navbar-light bg-light px-5">
                        <div>
                            <a href="{% url "tasks" %}" style="text-decoration: none;">
                                <h3 class="text-body">Django Task Management System</h3>
                            </a>
                            {% if request.user.is_authenticated %}
                            <h5 class="mb-0 me-2 text-body">
                                Hi, {% if request.user.first_name %}{{ request.user.first_name }}{% else %}{{ request.user.username }}{% endif %}
                            </h5>
                            {% endif %}
                        </div>
                        <div class="ms-auto d-flex align-items-center">
                            {% if request.user.is_authenticated %}
                            <a href="{% url 'add-new' %}" class="btn btn-success my-2 me-2">New task</a>
                            <form method="post" action="{% url 'logout' %}">
                                {% csrf_token %}
                                <button type="submit" class="nav-link btn btn-link text-danger no-global-style">Logout</button>
                            </form>
                            {% else %}
                            <div class="nav-item">
                                <a class="nav-link" href="{% url 'login' %}">Login</a>
                            </div>
                            {% endif %}
                        </div>
                    </nav>
            
                    <!-- Main content area, can be overridden in child templates -->
                    <div class="container mt-5 mb-5">
                        {% block content %}{% endblock %}
                    </div>
                </body>
            </html>
        
    
  • Create a directory in the **templates** directory with the name **base**. Now create files with the names **login.html**, **register.html**, **task_create.html**, **task_delete.html**, **task_detail.html**, **task_list.html**, and **task_update.html**. After creating all the files the base directory folder structure should be like this.
base-directory-folder-structure
  • Place these codes in the respective files.Place this code in the **login.html** file.
  • **login.html**: This file is the template file of the login page.
        <!-- base/templates/base/login.html -->

            {% extends "base.html" %} {% block title %} Login
            <!-- Sets the title to "Login" -->
            {% endblock %} {% block content %}
            
            <h1 class="h2">Login</h1>
            <form action="" method="post">
              {% csrf_token %}
              <!-- CSRF token for security -->
              {{ form.as_p }}
              <!-- Django form as paragraph -->
              <input type="submit" value="Login" />
              <!-- Submit button for the form -->
              <p
                style="color: #333; font-size: 1.2rem; text-align: center; margin-top: 20px;"
              >
                Don't have an account? <a href="{% url 'register' %}">Register</a>
              </p>
            </form>
            
            {% endblock %}
        
    
  • **register.html**: This file is the template file of the new user registration page.
        <!-- base/templates/base/register.html -->

            {% extends "base.html" %} {% block title %} Register
            <!-- Sets the title to "Register" -->
            {% endblock %} {% block content %}
            
            <h1 class="h2">New registration</h1>
            <form action="" method="post">
              {% csrf_token %}
              <!-- CSRF token for security -->
              {{ form.as_p }}
              <!-- Django form as paragraph -->
              <input type="submit" value="Register" />
              <!-- Submit button for the form -->
              <p
                style="color: #333; font-size: 1.2rem; text-align: center; margin-top: 20px;"
              >
                Already have an account? <a href="{% url 'login' %}">Login</a>
              </p>
            </form>
            
            {% endblock %}
        
    
  • **task_create.html**: This file is the template file of the add new tasks page where the registered user can add new tasks.
        <!-- base/templates/base/task_create.html -->

            {% extends "base.html" %} {% load static %}
            <!-- Load static files for the CKEditor -->
            
            <!-- Include CKEditor JavaScript file -->
            <script src="{% static 'ckeditor/ckeditor.js' %}"></script>
            
            {% block title %} New task {% endblock %} {% block content %}
            
            <h1 class="h2">New task</h1>
            
            <form action="" method="post">
              {% csrf_token %}
              <!-- CSRF token for security -->
              {{ form.media }}
              <!-- Form media -->
              {{ form.as_p }}
              <!-- Form fields -->
              <input type="submit" value="Finish" />
            </form>
            
            <!-- Initialize CKEditor -->
            <script>
                CKEDITOR.replace("id_description"); <!-- Replace textarea with id 'id_description' with CKEditor instance -->
            </script>
            
            {% endblock %}
        
    
  • **task_delete.html**: This file is the template file for the delete task page of the application.
        <!-- base/templates/base/task_delete.html -->

            {% extends "base.html" %} {% block title %} TMS - Delete Task {% endblock %} {%
            block content %}
            
            <h1 class="h2 text-danger">
              <b>Are you sure you want to delete this task?</b>
            </h1>
            
            <!-- Form for confirming task deletion -->
            <form action="" method="post">
              {% csrf_token %} {{ form.as_p }}
              <!-- Display task details -->
              <h3>Title</h3>
              <p class="p10">
                <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task}}
              </p>
              <h3>Description</h3>
              <p class="p10">
                <i class="fa fa-arrow-right" aria-hidden="true"></i>
                {{task.description|safe}}
              </p>
              <h3>Due date</h3>
              <p class="p10">
                <i class="fa fa-arrow-right" aria-hidden="true"></i> {{ task.due|date:"F j,
                Y, P" }}
              </p>
              <h3>Created on</h3>
              <p class="p10">
                <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.created|date:"F
                j, Y, P" }}
              </p>
              <h3>Last updated on</h3>
              <p class="p10">
                <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.updated|date:"F
                j, Y, P" }}
              </p>
              <h3>Status</h3>
              <p class="p10">
                <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.status}}
              </p>
            
              <input
                class="button btn-danger no-global-style"
                type="submit"
                value="Yes delete"
              />
            </form>
            
            {% endblock %}
        
    
  • **task_detail.html**: This file is the template file of the task details view page.
        <!-- base/templates/base/task_detail.html -->

            {% extends "base.html" %} {% block title %} {{task}} {% endblock %} {% block
            content %}
            
            <!-- Display task details -->
            <h3>Title</h3>
            <p class="p10"><i class="fa fa-arrow-right" aria-hidden="true"></i> {{task}}</p>
            <h3>Description</h3>
            <p class="p10">
              <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.description|safe}}
            </p>
            <h3>Due date</h3>
            <p class="p10">
              <i class="fa fa-arrow-right" aria-hidden="true"></i> {{ task.due|date:"F j, Y,
              P" }}
            </p>
            <h3>Created on</h3>
            <p class="p10">
              <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.created|date:"F j,
              Y, P" }}
            </p>
            <h3>Last updated on</h3>
            <p class="p10">
              <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.updated|date:"F j,
              Y, P" }}
            </p>
            <h3>Status</h3>
            <p class="p10">
              <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.status}}
            </p>
            
            {% endblock %}
        
    
  • **task_list.html**: This file is the template file of the home page where all the tasks are displayed.
        <!-- base/templates/base/task_list.html -->

            {% extends "base.html" %} {% block title %} Home {% endblock %} {% block content
            %}
            
            <style>
              /* Styles for the search bar and icons */
              .search-container {
                position: relative;
              }
              .search-icon {
                position: absolute;
                top: 50%;
                left: 10px;
                transform: translateY(-50%);
              }
              .search-input {
                padding-left: 30px;
                outline: none !important;
                box-shadow: none !important;
                height: 3rem;
              }
            
              .icon:hover {
                cursor: pointer;
                opacity: 0.7;
              }
              hr {
                border: 2px solid #212529;
                opacity: 100;
              }
            </style>
            
            <!-- Search bar form -->
            <form action="" method="GET">
              <div class="search-container">
                <i class="fa fa-search search-icon"></i>
                <input
                  type="text"
                  id="searchBar"
                  class="form-control search-input"
                  placeholder="Press Ctrl + K to search"
                  name="search"
                  value="{{search_input}}"
                  aria-label="Search"
                  onkeydown="if (event.keyCode == 13) { this.form.submit(); return false; }"
                />
              </div>
            </form>
            
            <!-- Task lists -->
            <div class="row mt-5">
              <!-- To Do tasks -->
              <div class="col-md-3">
                <h2 class="text-center mb-3">
                  <i class="fa fa-list" aria-hidden="true"></i> To Do
                </h2>
                <hr />
                {% if todo_count == 0 %}
                <p>No tasks in the "To Do" status.</p>
                {% else %} {% for x in tasks %} {% if x.status == "todo" %}
                <div class="card mb-3">
                  <div class="card-body">
                    <h4 class="card-title">{{ x.title }}</h4>
                    <p class="card-text">{{ x.description|safe|truncatechars:50 }}</p>
                    <p class="card-text">{{ x.due|date:"F j, Y, P" }}</p>
                    <div class="text-end pt-3">
                      <i
                        class="fa fa-eye fa-lg text-primary icon"
                        onclick="window.location.href='{% url 'task' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-pencil fa-lg text-secondary icon"
                        onclick="window.location.href='{% url 'task-update' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-trash fa-lg text-danger icon"
                        onclick="window.location.href='{% url 'task-delete' x.id %}'"
                        aria-hidden="true"
                      ></i>
                    </div>
                  </div>
                </div>
                {% endif %} {% endfor %} {% endif %}
              </div>
              <!-- Started tasks -->
              <div class="col-md-3">
                <h2 class="text-center mb-3">
                  <i class="fa fa-tasks" aria-hidden="true"></i> Started
                </h2>
                <hr />
                {% if started_count == 0 %}
                <p>No tasks in the "Started" status.</p>
                {% else %} {% for x in tasks %} {% if x.status == "started" %}
                <div class="card mb-3">
                  <div class="card-body">
                    <h4 class="card-title">{{ x.title }}</h4>
                    <p class="card-text">{{ x.description|safe|truncatechars:50 }}</p>
                    <p class="card-text">{{ x.due|date:"F j, Y, P" }}</p>
                    <div class="text-end pt-3">
                      <i
                        class="fa fa-eye fa-lg text-primary icon"
                        onclick="window.location.href='{% url 'task' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-pencil fa-lg text-secondary icon"
                        onclick="window.location.href='{% url 'task-update' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-trash fa-lg text-danger icon"
                        onclick="window.location.href='{% url 'task-delete' x.id %}'"
                        aria-hidden="true"
                      ></i>
                    </div>
                  </div>
                </div>
                {% endif %} {% endfor %} {% endif %}
              </div>
              <!-- Completed tasks -->
              <div class="col-md-3">
                <h2 class="text-center mb-3">
                  <i class="fa fa-check" aria-hidden="true"></i> Complete
                </h2>
                <hr />
                {% if completed_count == 0 %}
                <p>No tasks in the "Completed" status.</p>
                {% else %} {% for x in tasks %} {% if x.status == "complete" %}
                <div class="card mb-3">
                  <div class="card-body">
                    <h4 class="card-title">{{ x.title }}</h4>
                    <p class="card-text">{{ x.description|safe|truncatechars:50 }}</p>
                    <p class="card-text">{{ x.due|date:"F j, Y, P" }}</p>
                    <div class="text-end pt-3">
                      <i
                        class="fa fa-eye fa-lg text-primary icon"
                        onclick="window.location.href='{% url 'task' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-pencil fa-lg text-secondary icon"
                        onclick="window.location.href='{% url 'task-update' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-trash fa-lg text-danger icon"
                        onclick="window.location.href='{% url 'task-delete' x.id %}'"
                        aria-hidden="true"
                      ></i>
                    </div>
                  </div>
                </div>
                {% endif %} {% endfor %} {% endif %}
              </div>
              <!-- Archived tasks -->
              <div class="col-md-3">
                <h2 class="text-center mb-3">
                  <i class="fa fa-archive" aria-hidden="true"></i> Archived
                </h2>
                <hr />
                {% if archived_count == 0 %}
                <p>No tasks in the "Archived" status.</p>
                {% else %} {% for x in tasks %} {% if x.status == "archived" %}
                <div class="card mb-3">
                  <div class="card-body">
                    <h4 class="card-title">{{ x.title }}</h4>
                    <p class="card-text">{{ x.description|safe|truncatechars:50 }}</p>
                    <p class="card-text">{{ x.due|date:"F j, Y, P" }}</p>
                    <div class="text-end pt-3">
                      <i
                        class="fa fa-eye fa-lg text-primary icon"
                        onclick="window.location.href='{% url 'task' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-pencil fa-lg text-secondary icon"
                        onclick="window.location.href='{% url 'task-update' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-trash fa-lg text-danger icon"
                        onclick="window.location.href='{% url 'task-delete' x.id %}'"
                        aria-hidden="true"
                      ></i>
                    </div>
                  </div>
                </div>
                {% endif %} {% endfor %} {% endif %}
              </div>
            </div>
            
            <!-- Script to focus the search bar when Ctrl + K is pressed -->
            <script>
                document.addEventListener("keydown", function (event) {
                    if (event.ctrlKey && event.key === "k") {
                        event.preventDefault();
                        var searchBar = document.getElementById("searchBar");
                        searchBar.focus();
                        // Set cursor at the end of the input field
                        searchBar.setSelectionRange(
                            searchBar.value.length,
                            searchBar.value.length,
                        );
                    }
                });
            </script>
            
            {% endblock %}
        
    
  • **task_update.html**: This file is the template file for the task update page where the registered user can update their task.
        <!-- base/templates/base/task_update.html -->

            {% extends "base.html" %} {% load static %}
            <!-- Load static files for CKEditor -->
            
            <!-- Include CKEditor JavaScript file -->
            <script src="{% static 'ckeditor/ckeditor.js' %}"></script>
            
            {% block title %} Update {% endblock %} {% block content %}
            
            <h1 class="h2">Update Task</h1>
            
            <!-- Form for updating task -->
            <form action="" method="post">
              {% csrf_token %} {{ form.media }} {{ form.as_p }}
              <input type="submit" value="Update Task" />
              <!-- Submit button -->
            </form>
            
            <!-- Initialize CKEditor -->
            <script>
                CKEDITOR.replace("id_description"); <!-- Replace textarea with id 'id_description' with CKEditor instance -->
            </script>
            
            {% endblock %}
        
    

  • `python manage.py makemigrations` in the terminal to create a package of all the changes we made in the **models.py** file of the **base** application.
  • Execute `python manage.py migrate` in the terminal to apply all the changes to the database schema.

Django is a very robust full-stack web framework. It uses Python on the server side and provides API to integrate with almost any front-end web frameworks like React, Next, Vue, and Angular. In Django developers use MVT(Model View Template) architecture to build an application. In this project on Task Management Systems using Django, we are going to stick with built-in Django templates that use the Django Template Language. This project is a complete demonstration of Task Management Systems where the user can create their account and log in to the application. In the tasks section user can create a task with the rich text editor. From the home page, the user is capable of viewing, editing, and deletion a particular task.

Project Overview

Technologies and Skills Required to Implement this project

  1. Python
  2. Django web framework
  3. SQLite
  4. Bootstrap
  5. HTML
  6. CSS
  7. JavaScript

Building Django task management system

This project will have limited functionalities of a regular task management system software or application. The user can create an account and log in to the application with proper form validation of each field. After login, the user will be automatically redirected to the home or all tasks section of the application containing four columns To Do, Started, Complete, and Archive. Initially, tasks will be empty and the user can add new tasks with the task details. The project also includes a strict authentication system as all the routes are protected thus unauthorized users can not access any page and will be automatically been redirected to the login page.

ER Diagram for Django Task Management System

User interface use cases

  1. The user creates a new account
  2. User login to an extent account
  3. Add new task
  4. Update existing task
  5. View task details
  6. Delete task
  7. Rich Text Editor support for user

Admin interface use cases

  1. Create new account
  2. Login as administrator
  3. Admin can perform CRUD operation on all the data
  4. Manage user details
  5. Manage user permissions

System requirements

  1. The system must have an updated version of Python and PIP installed. Here we are using Python 3.11.7 and PIP 23.3.2.
  2. One pip package we need to run this project smoothly is virtualenv 20.25.0.
  3. We are using VS Code Editor but it's not the only one but rather the preferred one.

Steps to implement this project

  • Create a new directory named django_task_management_system.
  • Open any terminal in the directory django_task_management_system and run this command python -m venv venv to create a new virtual environment specifically for this project. We can also run the application without this step but it's not recommended to do so as per the Django Official Documentation.
  • Now run code .  to open the directory in VS Code. Till now if everything is executed properly then the output must be like this.

  • To activate the virtual environment run venv/Scripts/activate , this step might ask for some permission, or the operating system might find it a threat but we have to allow it.

  • Execute `pip install django, django-ckeditor`, this command will install Django and ckeditor in the project environment.
  • Run `django-admin startproject django_task_management_system`, it will create a new Django application in the working directory of the terminal.

  • Move the manage.py  file and the django_task_management_system directory to the parent directory and the folder structure should be like in the image.
  • Execute python manage.py startapp base in the terminal, it will create an app with the name base.
  • Run python manage.py runserver , this will start a local server at localhost:8000 and will display a Django start page like this. Some errors might occur in the terminal but those are nothing to worry about.

  • Turn off the running local server by pressing ctrl + c and let's start creating the application.
  • Open the settings.py file in the django_task_management_system directory and replace the default code with this code.

		# django_task_management_system/settings.py
            """
            Django settings for django_task_management_system project.
            
            Generated by 'django-admin startproject' using Django 5.0.
            
            For more information on this file, see
            https://docs.djangoproject.com/en/5.0/topics/settings/
            
            For the full list of settings and their values, see
            https://docs.djangoproject.com/en/5.0/ref/settings/
            """
            
            from pathlib import Path
            
            # Build paths inside the project like this: BASE_DIR / 'subdir'.
            BASE_DIR = Path(__file__).resolve().parent.parent
            
            # SECURITY WARNING: keep the secret key used in production secret!
            SECRET_KEY = 'django-insecure-#kfz(&+ra1by8r1fbqlg=z$3w+@a$iq&89&0w*+(o81@&=n#%p'
            
            # SECURITY WARNING: don't run with debug turned on in production!
            DEBUG = True
            
            ALLOWED_HOSTS = []
            
            # Application definition
            
            INSTALLED_APPS = [
                'django.contrib.admin',
                'django.contrib.auth',
                'django.contrib.contenttypes',
                'django.contrib.sessions',
                'django.contrib.messages',
                'django.contrib.staticfiles',
            ]
            
            # Custom apps
            # These are the apps that we have added to our project
            CUSTOM_APPS = [
                'base.apps.BaseConfig',
                'ckeditor',
            ]
            
            # Add custom apps to the list of installed apps
            # This line adds our custom apps to the list of installed apps
            INSTALLED_APPS += CUSTOM_APPS
            
            MIDDLEWARE = [
                'django.middleware.security.SecurityMiddleware',
                'django.contrib.sessions.middleware.SessionMiddleware',
                'django.middleware.common.CommonMiddleware',
                'django.middleware.csrf.CsrfViewMiddleware',
                'django.contrib.auth.middleware.AuthenticationMiddleware',
                'django.contrib.messages.middleware.MessageMiddleware',
                'django.middleware.clickjacking.XFrameOptionsMiddleware',
            ]
            
            ROOT_URLCONF = 'django_task_management_system.urls'
            
            TEMPLATES = [
                {
                    'BACKEND': 'django.template.backends.django.DjangoTemplates',
                    'DIRS': [],
                    'APP_DIRS': True,
                    'OPTIONS': {
                        'context_processors': [
                            'django.template.context_processors.debug',
                            'django.template.context_processors.request',
                            'django.contrib.auth.context_processors.auth',
                            'django.contrib.messages.context_processors.messages',
                        ],
                    },
                },
            ]
            
            WSGI_APPLICATION = 'django_task_management_system.wsgi.application'
            
            # Database
            # https://docs.djangoproject.com/en/5.0/ref/settings/#databases
            
            DATABASES = {
                'default': {
                    'ENGINE': 'django.db.backends.sqlite3',
                    'NAME': BASE_DIR / 'db.sqlite3',
                }
            }
            
            # Password validation
            # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
            
            AUTH_PASSWORD_VALIDATORS = [
                {
                    'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
                },
                {
                    'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
                },
                {
                    'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
                },
                {
                    'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
                },
            ]
            
            # Internationalization
            # https://docs.djangoproject.com/en/5.0/topics/i18n/
            
            LANGUAGE_CODE = 'en-us'
            
            TIME_ZONE = 'UTC'
            
            USE_I18N = True
            
            USE_TZ = True
            
            # Login url
            # This is the URL where our login view is located
            LOGIN_URL = 'login'
            
            # Static files (CSS, JavaScript, Images)
            # https://docs.djangoproject.com/en/5.0/howto/static-files/
            
            STATIC_URL = 'static/'
            
            # Default primary key field type
            # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
            
            DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
  
	
  • Place this code in the django_task_management_system/urls.py file.

		# django_task_management_system/urls.py

            # Import the admin module from django.contrib
            from django.contrib import admin
            # Import the include and path functions from django.urls
            from django.urls import include, path
            
            # Define the URL patterns for this module
            urlpatterns = [
                # When the 'admin/' URL is requested, Django will serve the built-in admin site
                path('admin/', admin.site.urls),
                # When the root URL ('') is requested, Django will look for URLs in 'base.urls'
                path('', include('base.urls')),
            ]
    
	
  • Create a file name urls.py in the base directory and place this code in that file.
		# base/urls.py
        
            from django.urls import path
            # Import the views from the current directory's views.py
            from .views import TaskListView, TaskDetailView, TaskCreateView, TaskUpdateView, TaskDeleteView, CustomLoginView, RegisterPage
            from django.contrib.auth.views import LogoutView
  
            # Define the URL patterns for this module or application
            urlpatterns = [
                path('login/', CustomLoginView.as_view(), name='login'),
                path('register/', RegisterPage.as_view(), name='register'),
                path('logout/', LogoutView.as_view(next_page='login'), name='logout'),
                path('', TaskListView.as_view(), name='tasks'),
                path('task/<int:pk>/', TaskDetailView.as_view(), name='task'),
                path('add-new/', TaskCreateView.as_view(), name='add-new'),
                path('task-update/<int:pk>/', TaskUpdateView.as_view(), name='task-update'),
                path('task-delete/<int:pk>/', TaskDeleteView.as_view(), name='task-delete'),
            ]
    	
						
					
  • Place this code in the base/views.py file.
        # base/views.py
            # Import necessary modules from Django
from django.shortcuts import redirect
from django.views.generic import ListView, DetailView
from django.views.generic.edit import CreateView, UpdateView, DeleteView, FormView
from django.urls import reverse_lazy
from django import forms
from ckeditor.widgets import CKEditorWidget
from django.contrib.auth.models import User
from django.contrib.auth.views import LoginView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth import login
from django.contrib.auth.forms import UserCreationForm

# Import Task model from the current directory
from .models import Task

# Custom login view
class CustomLoginView(LoginView):
    template_name = 'base/login.html'  # Use custom login template
    fields = '__all__'
    redirect_authenticated_user = True  # Redirect user if already authenticated

    # Redirect to tasks page after successful login
    def get_success_url(self):
        return reverse_lazy('tasks')

# Custom user creation form with additional fields
class CustomUserCreationForm(UserCreationForm):
    email = forms.EmailField(required=True)
    first_name = forms.CharField(required=True)
    last_name = forms.CharField(required=True)

    class Meta:
        model = User
        fields = ("username", "first_name", "last_name", "email", "password1", "password2")  # Include additional fields

    # Save the additional fields to the user model
    def save(self, commit=True):
        user = super(CustomUserCreationForm, self).save(commit=False)
        user.email = self.cleaned_data['email']
        user.first_name = self.cleaned_data['first_name']
        user.last_name = self.cleaned_data['last_name']
        if commit:
            user.save()
        return user

# Registration page view
class RegisterPage(FormView):
    template_name = 'base/register.html'  # Use custom registration template
    form_class = CustomUserCreationForm  # Use custom user creation form
    redirect_authenticated_user = True  # Redirect user if already authenticated
    success_url = reverse_lazy('tasks')  # Redirect to tasks page after successful registration

    # Log in the user after successful registration
    def form_valid(self, form):
        user = form.save()
        if user is not None:
            login(self.request, user)
        return super(RegisterPage, self).form_valid(form)

    # Redirect to tasks page if user is already authenticated
    def get(self, *args, **kwargs):
        if self.request.user.is_authenticated:
            return redirect('tasks')
        return super(RegisterPage, self).get(*args, **kwargs)

# Task list view
class TaskListView(LoginRequiredMixin, ListView):
    model = Task
    context_object_name = 'tasks'  # Use 'tasks' as context object name

    # Filter tasks by user and search input
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['tasks'] = context['tasks'].filter(user=self.request.user)  # Filter tasks by user
        search_input = self.request.GET.get('search') or ''
        if search_input:
            context['tasks'] = context['tasks'].filter(title__icontains=search_input)  # Filter tasks by search input
        context['search_input'] = search_input

        # Add the count for each status type
        context['todo_count'] = context['tasks'].filter(status='todo').count()
        context['started_count'] = context['tasks'].filter(status='started').count()
        context['completed_count'] = context['tasks'].filter(status='complete').count()
        context['archived_count'] = context['tasks'].filter(status='archived').count()

        return context

# Task detail view
class TaskDetailView(LoginRequiredMixin, DetailView):
    model = Task
    context_object_name = 'task'  # Use 'task' as context object name

# Task creation view
class TaskCreateView(LoginRequiredMixin, CreateView):
    model = Task
    template_name = 'base/task_create.html'  # Use custom task creation template
    success_url = reverse_lazy('tasks')  # Redirect to tasks page after successful task creation
    form_class = forms.modelform_factory(model, fields=['title', 'description', 'status', 'due'],
                                         widgets={'due': forms.DateTimeInput(attrs={'type': 'datetime-local'})})  # Use custom form

    # Set the task's user to the current user
    def form_valid(self, form):
        form.instance.user = self.request.user
        return super(TaskCreateView, self).form_valid(form)

# Task update view
class TaskUpdateView(LoginRequiredMixin, UpdateView):
    model = Task  # Use Task model
    template_name = 'base/task_update.html'  # Use custom task update template
    success_url = reverse_lazy('tasks')  # Redirect to tasks page after successful task update
    # Use a form with 'title', 'description', 'status', 'due' fields and custom widgets for 'due' and 'description' fields
    form_class = forms.modelform_factory(Task, fields=['title', 'description', 'status', 'due'],
                                         widgets={'due': forms.DateTimeInput(attrs={'type': 'datetime-local'}),
                                                  'description': CKEditorWidget()})

# Task deletion view
class TaskDeleteView(LoginRequiredMixin, DeleteView):
    model = Task  # Use Task model
    context_object_name = 'task'  # Use 'task' as context object name
    template_name = 'base/task_delete.html'  # Use custom task deletion template
    success_url = reverse_lazy('tasks')  # Redirect to tasks page after successful task deletion
    
    
  • Place this code in the base/models.py file.
        # base/models.py

            from django.db import models
            from django.contrib.auth.models import User
            from ckeditor.fields import RichTextField
            
            # Define choices for task status
            TASK_STATUS_CHOICES =  (
                ('todo', 'To Do'),
                ('started', 'Started'),
                ('complete', 'Complete'),
                ('archived', 'Archived'),
            )
            
            # Define Task model
            class Task(models.Model):
                user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True)
                title = models.CharField(max_length=200)
                description = RichTextField(blank=True, null=True) # RichTextField is a WYSIWYG editor
                status = models.CharField(max_length=20, choices=TASK_STATUS_CHOICES, default='todo')
                due = models.DateTimeField(null=True, blank=True)
                created = models.DateTimeField(auto_now_add=True)
                updated = models.DateTimeField(auto_now=True)
            
                # String representation of the task
                def __str__(self):
                    return self.title  # Use the task's title as its string representation
            
                # Meta data for the Task model
                class Meta:
                    ordering = ['-created']  # Order tasks by the 'created' field in descending order
    
  • Place this code in the **admin.py** file of the **base** directory of the application.
        # base/admin.py

            from django.contrib import admin
            from .models import Task
            
            # Register the Task model with the admin site
            admin.site.register(Task)
    
  • - Create a directory name **templates** in the **base** directory and add a file named **base.html** then place this code in the **base.html** file.
        <!-- base/templates/base.html -->

            <!DOCTYPE html>
            <html>
                <head>
                    <!-- Meta tags for character encoding, compatibility, viewport settings -->
                    <meta charset="utf-8">
                    <meta http-equiv="X-UA-Compatible" content="IE=edge">
                    <meta name="viewport" content="width=device-width, initial-scale=1">
            
                    <!-- Title of the page, can be overridden in child templates -->
                    <title>DTMS - {% block title %}{% endblock %}</title>
            
                    <!-- Link to Bootstrap CSS and JS, Google Fonts, Font Awesome and custom favicon -->
                    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
                    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"></script>
                    <link href="https://fonts.googleapis.com/css2?family=Raleway:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap" rel="stylesheet">
                    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
                    <link rel="icon" href="https://media.geeksforgeeks.org/gfg-gg-logo.svg">
                </head>
            
                <!-- Custom styles for body, form elements, and CKEditor -->
                <style>
                    body {
                        font-family: 'Raleway', sans-serif;
                    }
            
                    /* Style all form elements */
                    form {
                        margin: 20px 0;
                    }
            
                    /* Style all input fields, select dropdowns, and textareas */
                    form input,
                    form select,
                    form textarea {
                        width: 100%;
                        padding: 10px;
                        margin: 10px 0;
                        border: 1px solid #ccc;
                        border-radius: 4px;
                    }
            
                    /* Style submit buttons */
                    form input[type="submit"]:not(.no-global-style) {
                        background-color: #4CAF50 !important;
                        color: white;
                        padding: 10px 20px;
                        border: none;
                        border-radius: 4px;
                        cursor: pointer;
                        margin: 10px 0;
                    }
            
                    /* Add a hover effect for submit buttons */
                    form input[type="submit"]:not(.no-global-style):hover {
                        background-color: #0d6efd;
                    }
            
            
                    /* CKEditor styles */
                    .django-ckeditor-widget{
                        width: 100%;
                    }
            
                </style>
            
                <body>
                    <!-- Navigation bar -->
                    <nav class="navbar navbar-expand-lg navbar-light bg-light px-5">
                        <div>
                            <a href="{% url "tasks" %}" style="text-decoration: none;">
                                <h3 class="text-body">Django Task Management System</h3>
                            </a>
                            {% if request.user.is_authenticated %}
                            <h5 class="mb-0 me-2 text-body">
                                Hi, {% if request.user.first_name %}{{ request.user.first_name }}{% else %}{{ request.user.username }}{% endif %}
                            </h5>
                            {% endif %}
                        </div>
                        <div class="ms-auto d-flex align-items-center">
                            {% if request.user.is_authenticated %}
                            <a href="{% url 'add-new' %}" class="btn btn-success my-2 me-2">New task</a>
                            <form method="post" action="{% url 'logout' %}">
                                {% csrf_token %}
                                <button type="submit" class="nav-link btn btn-link text-danger no-global-style">Logout</button>
                            </form>
                            {% else %}
                            <div class="nav-item">
                                <a class="nav-link" href="{% url 'login' %}">Login</a>
                            </div>
                            {% endif %}
                        </div>
                    </nav>
            
                    <!-- Main content area, can be overridden in child templates -->
                    <div class="container mt-5 mb-5">
                        {% block content %}{% endblock %}
                    </div>
                </body>
            </html>
        
    
  • Create a directory in the **templates** directory with the name **base**. Now create files with the names **login.html**, **register.html**, **task_create.html**, **task_delete.html**, **task_detail.html**, **task_list.html**, and **task_update.html**. After creating all the files the base directory folder structure should be like this.
base-directory-folder-structure
  • Place these codes in the respective files.Place this code in the **login.html** file.
  • **login.html**: This file is the template file of the login page.
        <!-- base/templates/base/login.html -->

            {% extends "base.html" %} {% block title %} Login
            <!-- Sets the title to "Login" -->
            {% endblock %} {% block content %}
            
            <h1 class="h2">Login</h1>
            <form action="" method="post">
              {% csrf_token %}
              <!-- CSRF token for security -->
              {{ form.as_p }}
              <!-- Django form as paragraph -->
              <input type="submit" value="Login" />
              <!-- Submit button for the form -->
              <p
                style="color: #333; font-size: 1.2rem; text-align: center; margin-top: 20px;"
              >
                Don't have an account? <a href="{% url 'register' %}">Register</a>
              </p>
            </form>
            
            {% endblock %}
        
    
  • **register.html**: This file is the template file of the new user registration page.
        <!-- base/templates/base/register.html -->

            {% extends "base.html" %} {% block title %} Register
            <!-- Sets the title to "Register" -->
            {% endblock %} {% block content %}
            
            <h1 class="h2">New registration</h1>
            <form action="" method="post">
              {% csrf_token %}
              <!-- CSRF token for security -->
              {{ form.as_p }}
              <!-- Django form as paragraph -->
              <input type="submit" value="Register" />
              <!-- Submit button for the form -->
              <p
                style="color: #333; font-size: 1.2rem; text-align: center; margin-top: 20px;"
              >
                Already have an account? <a href="{% url 'login' %}">Login</a>
              </p>
            </form>
            
            {% endblock %}
        
    
  • **task_create.html**: This file is the template file of the add new tasks page where the registered user can add new tasks.
        <!-- base/templates/base/task_create.html -->

            {% extends "base.html" %} {% load static %}
            <!-- Load static files for the CKEditor -->
            
            <!-- Include CKEditor JavaScript file -->
            <script src="{% static 'ckeditor/ckeditor.js' %}"></script>
            
            {% block title %} New task {% endblock %} {% block content %}
            
            <h1 class="h2">New task</h1>
            
            <form action="" method="post">
              {% csrf_token %}
              <!-- CSRF token for security -->
              {{ form.media }}
              <!-- Form media -->
              {{ form.as_p }}
              <!-- Form fields -->
              <input type="submit" value="Finish" />
            </form>
            
            <!-- Initialize CKEditor -->
            <script>
                CKEDITOR.replace("id_description"); <!-- Replace textarea with id 'id_description' with CKEditor instance -->
            </script>
            
            {% endblock %}
        
    
  • **task_delete.html**: This file is the template file for the delete task page of the application.
        <!-- base/templates/base/task_delete.html -->

            {% extends "base.html" %} {% block title %} TMS - Delete Task {% endblock %} {%
            block content %}
            
            <h1 class="h2 text-danger">
              <b>Are you sure you want to delete this task?</b>
            </h1>
            
            <!-- Form for confirming task deletion -->
            <form action="" method="post">
              {% csrf_token %} {{ form.as_p }}
              <!-- Display task details -->
              <h3>Title</h3>
              <p class="p10">
                <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task}}
              </p>
              <h3>Description</h3>
              <p class="p10">
                <i class="fa fa-arrow-right" aria-hidden="true"></i>
                {{task.description|safe}}
              </p>
              <h3>Due date</h3>
              <p class="p10">
                <i class="fa fa-arrow-right" aria-hidden="true"></i> {{ task.due|date:"F j,
                Y, P" }}
              </p>
              <h3>Created on</h3>
              <p class="p10">
                <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.created|date:"F
                j, Y, P" }}
              </p>
              <h3>Last updated on</h3>
              <p class="p10">
                <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.updated|date:"F
                j, Y, P" }}
              </p>
              <h3>Status</h3>
              <p class="p10">
                <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.status}}
              </p>
            
              <input
                class="button btn-danger no-global-style"
                type="submit"
                value="Yes delete"
              />
            </form>
            
            {% endblock %}
        
    
  • **task_detail.html**: This file is the template file of the task details view page.
        <!-- base/templates/base/task_detail.html -->

            {% extends "base.html" %} {% block title %} {{task}} {% endblock %} {% block
            content %}
            
            <!-- Display task details -->
            <h3>Title</h3>
            <p class="p10"><i class="fa fa-arrow-right" aria-hidden="true"></i> {{task}}</p>
            <h3>Description</h3>
            <p class="p10">
              <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.description|safe}}
            </p>
            <h3>Due date</h3>
            <p class="p10">
              <i class="fa fa-arrow-right" aria-hidden="true"></i> {{ task.due|date:"F j, Y,
              P" }}
            </p>
            <h3>Created on</h3>
            <p class="p10">
              <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.created|date:"F j,
              Y, P" }}
            </p>
            <h3>Last updated on</h3>
            <p class="p10">
              <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.updated|date:"F j,
              Y, P" }}
            </p>
            <h3>Status</h3>
            <p class="p10">
              <i class="fa fa-arrow-right" aria-hidden="true"></i> {{task.status}}
            </p>
            
            {% endblock %}
        
    
  • **task_list.html**: This file is the template file of the home page where all the tasks are displayed.
        <!-- base/templates/base/task_list.html -->

            {% extends "base.html" %} {% block title %} Home {% endblock %} {% block content
            %}
            
            <style>
              /* Styles for the search bar and icons */
              .search-container {
                position: relative;
              }
              .search-icon {
                position: absolute;
                top: 50%;
                left: 10px;
                transform: translateY(-50%);
              }
              .search-input {
                padding-left: 30px;
                outline: none !important;
                box-shadow: none !important;
                height: 3rem;
              }
            
              .icon:hover {
                cursor: pointer;
                opacity: 0.7;
              }
              hr {
                border: 2px solid #212529;
                opacity: 100;
              }
            </style>
            
            <!-- Search bar form -->
            <form action="" method="GET">
              <div class="search-container">
                <i class="fa fa-search search-icon"></i>
                <input
                  type="text"
                  id="searchBar"
                  class="form-control search-input"
                  placeholder="Press Ctrl + K to search"
                  name="search"
                  value="{{search_input}}"
                  aria-label="Search"
                  onkeydown="if (event.keyCode == 13) { this.form.submit(); return false; }"
                />
              </div>
            </form>
            
            <!-- Task lists -->
            <div class="row mt-5">
              <!-- To Do tasks -->
              <div class="col-md-3">
                <h2 class="text-center mb-3">
                  <i class="fa fa-list" aria-hidden="true"></i> To Do
                </h2>
                <hr />
                {% if todo_count == 0 %}
                <p>No tasks in the "To Do" status.</p>
                {% else %} {% for x in tasks %} {% if x.status == "todo" %}
                <div class="card mb-3">
                  <div class="card-body">
                    <h4 class="card-title">{{ x.title }}</h4>
                    <p class="card-text">{{ x.description|safe|truncatechars:50 }}</p>
                    <p class="card-text">{{ x.due|date:"F j, Y, P" }}</p>
                    <div class="text-end pt-3">
                      <i
                        class="fa fa-eye fa-lg text-primary icon"
                        onclick="window.location.href='{% url 'task' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-pencil fa-lg text-secondary icon"
                        onclick="window.location.href='{% url 'task-update' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-trash fa-lg text-danger icon"
                        onclick="window.location.href='{% url 'task-delete' x.id %}'"
                        aria-hidden="true"
                      ></i>
                    </div>
                  </div>
                </div>
                {% endif %} {% endfor %} {% endif %}
              </div>
              <!-- Started tasks -->
              <div class="col-md-3">
                <h2 class="text-center mb-3">
                  <i class="fa fa-tasks" aria-hidden="true"></i> Started
                </h2>
                <hr />
                {% if started_count == 0 %}
                <p>No tasks in the "Started" status.</p>
                {% else %} {% for x in tasks %} {% if x.status == "started" %}
                <div class="card mb-3">
                  <div class="card-body">
                    <h4 class="card-title">{{ x.title }}</h4>
                    <p class="card-text">{{ x.description|safe|truncatechars:50 }}</p>
                    <p class="card-text">{{ x.due|date:"F j, Y, P" }}</p>
                    <div class="text-end pt-3">
                      <i
                        class="fa fa-eye fa-lg text-primary icon"
                        onclick="window.location.href='{% url 'task' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-pencil fa-lg text-secondary icon"
                        onclick="window.location.href='{% url 'task-update' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-trash fa-lg text-danger icon"
                        onclick="window.location.href='{% url 'task-delete' x.id %}'"
                        aria-hidden="true"
                      ></i>
                    </div>
                  </div>
                </div>
                {% endif %} {% endfor %} {% endif %}
              </div>
              <!-- Completed tasks -->
              <div class="col-md-3">
                <h2 class="text-center mb-3">
                  <i class="fa fa-check" aria-hidden="true"></i> Complete
                </h2>
                <hr />
                {% if completed_count == 0 %}
                <p>No tasks in the "Completed" status.</p>
                {% else %} {% for x in tasks %} {% if x.status == "complete" %}
                <div class="card mb-3">
                  <div class="card-body">
                    <h4 class="card-title">{{ x.title }}</h4>
                    <p class="card-text">{{ x.description|safe|truncatechars:50 }}</p>
                    <p class="card-text">{{ x.due|date:"F j, Y, P" }}</p>
                    <div class="text-end pt-3">
                      <i
                        class="fa fa-eye fa-lg text-primary icon"
                        onclick="window.location.href='{% url 'task' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-pencil fa-lg text-secondary icon"
                        onclick="window.location.href='{% url 'task-update' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-trash fa-lg text-danger icon"
                        onclick="window.location.href='{% url 'task-delete' x.id %}'"
                        aria-hidden="true"
                      ></i>
                    </div>
                  </div>
                </div>
                {% endif %} {% endfor %} {% endif %}
              </div>
              <!-- Archived tasks -->
              <div class="col-md-3">
                <h2 class="text-center mb-3">
                  <i class="fa fa-archive" aria-hidden="true"></i> Archived
                </h2>
                <hr />
                {% if archived_count == 0 %}
                <p>No tasks in the "Archived" status.</p>
                {% else %} {% for x in tasks %} {% if x.status == "archived" %}
                <div class="card mb-3">
                  <div class="card-body">
                    <h4 class="card-title">{{ x.title }}</h4>
                    <p class="card-text">{{ x.description|safe|truncatechars:50 }}</p>
                    <p class="card-text">{{ x.due|date:"F j, Y, P" }}</p>
                    <div class="text-end pt-3">
                      <i
                        class="fa fa-eye fa-lg text-primary icon"
                        onclick="window.location.href='{% url 'task' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-pencil fa-lg text-secondary icon"
                        onclick="window.location.href='{% url 'task-update' x.id %}'"
                        aria-hidden="true"
                      ></i>
                      <i
                        class="fa fa-trash fa-lg text-danger icon"
                        onclick="window.location.href='{% url 'task-delete' x.id %}'"
                        aria-hidden="true"
                      ></i>
                    </div>
                  </div>
                </div>
                {% endif %} {% endfor %} {% endif %}
              </div>
            </div>
            
            <!-- Script to focus the search bar when Ctrl + K is pressed -->
            <script>
                document.addEventListener("keydown", function (event) {
                    if (event.ctrlKey && event.key === "k") {
                        event.preventDefault();
                        var searchBar = document.getElementById("searchBar");
                        searchBar.focus();
                        // Set cursor at the end of the input field
                        searchBar.setSelectionRange(
                            searchBar.value.length,
                            searchBar.value.length,
                        );
                    }
                });
            </script>
            
            {% endblock %}
        
    
  • **task_update.html**: This file is the template file for the task update page where the registered user can update their task.
        <!-- base/templates/base/task_update.html -->

            {% extends "base.html" %} {% load static %}
            <!-- Load static files for CKEditor -->
            
            <!-- Include CKEditor JavaScript file -->
            <script src="{% static 'ckeditor/ckeditor.js' %}"></script>
            
            {% block title %} Update {% endblock %} {% block content %}
            
            <h1 class="h2">Update Task</h1>
            
            <!-- Form for updating task -->
            <form action="" method="post">
              {% csrf_token %} {{ form.media }} {{ form.as_p }}
              <input type="submit" value="Update Task" />
              <!-- Submit button -->
            </form>
            
            <!-- Initialize CKEditor -->
            <script>
                CKEDITOR.replace("id_description"); <!-- Replace textarea with id 'id_description' with CKEditor instance -->
            </script>
            
            {% endblock %}
        
    
  • `python manage.py makemigrations` in the terminal to create a package of all the changes we made in the **models.py** file of the **base** application.
  • Execute `python manage.py migrate` in the terminal to apply all the changes to the database schema.
terminal-output-of-makemigrations-and-migrate-operations
  • Terminal output of makemigrations and migrate operations.
  • Run `python manage.py runserver` to start the local development server of Django to view the application we created.
terminal-output-of-the-running-local-development-server
  • Terminal output of the running local development server.

Output

By following along through this article you will be able to achieve results similar to the overview video. This project aims to create a basic version of a task management application using Django. In addition, additional features can be added to this version of the project using module features in Django.