Build A Simple Task Manager With Visualization In Django

A task management app, also known as a to-do list app, is a great way to get started with building web applications using the Django web framework. Django is a powerful and flexible framework that allows you to quickly create web applications with a clean and organized structure.

In a task management app, the tasks are typically stored in a database and the app provides a user interface for interacting with the data. The app can be built to include features such as the ability to create new tasks, view a list of existing tasks, edit or delete tasks, mark tasks as complete, set reminders or due dates, prioritize tasks, add notes or attachments, and share tasks with others.

Build a Simple Task Manager with Visualization in Django

Prerequisites

Here is a step-by-step guide for creating a simple task management app in Django.

1. Create a new Django project by running the following command in the command prompt:

django-admin startproject taskmanagement

2. Create a new app tasks within the project by running the following command:

python manage.py startapp tasks

3. In the tasks app's models.py file, create a model for the tasks by defining a class that inherits from models.Model. This class will represent a task and will have fields such as a detail, status, category & creation date. We have also defined status tupple with complete and incomplete values for choices being used in the status field.

class Task(models.Model):
  COMPLETE = "Complete"
  INCOMPLETE = "Incomplete"
  STATUS = [
    (COMPLETE, "Complete"),
    (INCOMPLETE, "Incomplete")
  ]

  detail = models.CharField(max_length=200, null=False, blank=False)
  status = models.CharField(max_length=200, choices=STATUS,default=INCOMPLETE)
  category = models.CharField(max_length=200, default=None)
  creation_date = models.DateTimeField('Creation Date', default=now)

4. Create a new file in the tasks app called views.py. In this file, create views for getting, creating, updating, and deleting tasks.

def index(request):
  task_list = Task.objects.order_by('-creation_date')
  context = {'task_list': task_list}
  return render(request, 'task/index.html', context)

def add(request):
  if request.method == "POST":
    if(request.POST['detail'] != "" and request.POST['category'] != ""):
      task = Task(detail=request.POST['detail'], status=Task.INCOMPLETE, category=string.capwords(request.POST['category']))
      task.save()
      return HttpResponseRedirect(reverse('task:index'));

    messages.error(request, "Task Detail & Category can't be blank" )
  else:
    messages.error(request, "Wrong Method" )
  return HttpResponseRedirect(reverse('task:index'));

def delete(request):
  if request.method == "POST":
    post_body = json.loads(request.body)
    task = get_object_or_404(Task, pk=post_body.get("id"))
    task.delete()    
    return HttpResponse(json.dumps({
        "success": "deleted"
    }), content_type="application/json")  

  return HttpResponse(json.dumps({
      "error": "Method Not Supported"
  }), content_type="application/json")      

def update(request):
  if request.method == "POST":
    post_body = json.loads(request.body)
    task = get_object_or_404(Task, pk=post_body.get("id"))
    task.status = Task.COMPLETE if post_body.get("status") == "True" else Task.INCOMPLETE
    task.save(update_fields=['status'])
    return HttpResponse(json.dumps({
        "success": "updated"
    }), content_type="application/json")    

  return HttpResponse(json.dumps({
      "error": "Method Not Supported"
  }), content_type="application/json") 

def get_task(request):
  task_list = Task.objects.order_by('-creation_date')
  task_list_json = serializers.serialize('json', task_list)

  return HttpResponse(task_list_json, content_type="application/json")

5. Add index.html to the templates directory that displays a list of tasks.

{% load static %}
<h2 class="text-center">Task App</h2>
{% if messages %}
<ul class="messages">
  {% for message in messages %}
  <li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
  {% endfor %}
</ul>
{% endif %}
<form class="input-section" action="{% url 'task:add' %}" method="POST">
  {% csrf_token %}
  <input type="text" placeholder="Task Category" name="category" class=""/>
  <input type="text" placeholder="Task Detail" name="detail" class=""/>
  <button type="submit">+</button>
</form>
<div class="list-section">
{% if task_list %}
  <ul>
  {% for task in task_list %}
    <li class="card {% if task.status == 'Complete' %}completed{% endif %}" id="task-{{ task.id }}">
      <div class="task-info">
          <span><input type="checkbox" name="task-{{ task.id }}" class="status" {% if task.status == 'Complete' %}checked{% endif %}/> {{ task.detail }}  </span>
          <span class="task-category">{{ task.category }}</span>
      </div>
      <span class="delete-button">X</span>
    </li>
  {% endfor %}
  </ul>
{% else %}
  <p class="text-center">No Task to be Done</p>
{% endif %}
</div>

6. In the tasks app's urls.py file, define the URLs for the views that were created in views.py

from django.urls import path
from . import views
app_name = 'task'

urlpatterns = [
    path('', views.index, name='index'),
    path('add/', views.add, name='add'),
    path('update/', views.update, name='update'),
    path('delete/', views.delete, name='delete'),
    path('get-tasks/', views.get_task, name='get-tasks'),
]

7. Include task model to the database by running makemigrations command and sqlmigrate. After migrating, we can run our app.

$ python manage.py makemigrations tasks
$ python manage.py sqlmigrate tasks 0001
$ python manage.py migrate tasks
$ python manage.py runserver 8080

8. Create script.js file in the static folder that sends the ajax request using fetch API - for updating and deleting the tasks. Check Django documentation for more info on CSRF tokens. 

let csrftoken = getCookie('csrftoken');
// update the status of task
document.addEventListener('click', function(e) {
    if (Array.from(e.target.classList).includes('status')) {
        let fetchOptions = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': csrftoken
            },
            body: JSON.stringify({
                id: e.target.getAttribute("name").split("task-")[1],
                status: e.target.checked ? "True" : "False"
            })
        };
        fetch('/task/update/', fetchOptions).then(response => response.json()).then(data => {
            if (data.success) updateTaskList()
        });
    }
}, false);
// request to delete the task
document.addEventListener('click', function(e) {
    if (Array.from(e.target.classList).includes('delete-button')) {
        let fetchOptions = {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': csrftoken
            },
            body: JSON.stringify({
                id: e.target.parentElement.getAttribute("id").split("task-")[1]
            })
        };
        fetch('/task/delete/', fetchOptions).then(response => response.json()).then(data => {
            if (data.success) updateTaskList()
        });
    }
}, false);

function updateTaskList() {
    fetch('/task/get-tasks/').then(response => response.json()).then(data => {
        let domString = "<ul>";
        data.forEach(task => {
            domString += `
<li class="card ` + (task.fields.status == "Complete" ? 'completed' : '') + `" id="task-${task.pk}">
<div class="task-info">
<span><input type="checkbox" name="task-${task.pk}" class="status" ` + (task.fields.status == "Complete" ? 'checked' : '') + `/> ${task.fields.detail} </span>
<span class="task-category">${task.fields.category}</span>
</div>
<span class="delete-button">X</span>
</li>
`;
        });
        domString += "</ul>";
        document.querySelector(".list-section").innerHTML = domString;
    });
}

To make our app more intuitive, let's add a chart to show the status of tasks based on category. To generate a chart, let us use a JavaScript charting library such as CanvasJS and use Django's views and templates to render the chart.

1. In views.py file, create a view that will get the chart data from the task model and returns the response in JSON format.

def get_chart_data(request):
  task =  Task.objects.values('category').annotate(complete=Count("status", filter=Q(status=Task.COMPLETE)), incomplete=Count("status", filter=Q(status=Task.INCOMPLETE)))
  return HttpResponse(json.dumps(list(task)), content_type="application/json")

2. In index.html template, include CanvasJS & add a container in which the chart will be rendered.

<div id="chartContainer" style="width: 100%; height: 360px;"></div>
<script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script>

3. Get the data from API by making an ajax request and parse it in the format accepted by CanvasJS.

/* script.js */
function displayChart() {
    fetch("/task/get-chart-data").then(response => response.json()).then(dataArr => {
        chart.options.data[0].dataPoints = [];
        chart.options.data[1].dataPoints = [];
        dataArr.forEach(data => {
            chart.options.data[0].dataPoints.push({
                label: data["category"],
                y: data["complete"]
            })
            chart.options.data[1].dataPoints.push({
                label: data["category"],
                y: data["incomplete"]
            })
        });
        chart.render()
    })
}
var dps = {
        "complete": [],
        "incomplete": []
    },
    chart;
chart = new CanvasJS.Chart("chartContainer", {
    theme: "light2",
    animationEnabled: true,
    title: {
        text: "Task Status Based on Category"
    },
    toolTip: {
        shared: true
    },
    data: [{
        name: "Complete",
        type: "stackedColumn100",
        showInLegend: true,
        dataPoints: dps["complete"]
    }, {
        name: "Incomplete",
        type: "stackedColumn100",
        showInLegend: true,
        dataPoints: dps["incomplete"]
    }]
});
displayChart();

4. In the tasks app's urls.py file, we will define the URLs used to get the chart data.

path('get-chart-data/', views.get_chart_data, name='get-chart-data'),

Thus we have added a chart to our task management app to visualize our progress. Similarly, you can add more features to the app like by adding authentication and authorization, adding field to the task model such as priority, schedule date, sharing task with other people & many more. To customize the chart, you can play around with the options provided by CanvasJS. Check out their gallery which demonstrates a variety of customization available.


Similar Articles