How to Build A Todo App With Django
Django is an awesome and powerful python web framework! It is simply a framework for creating powerful web applications. A python web framework that does all the heavy work for you and it allows for fast website development by providing a broad range of tools and shortcuts out of the box. To see it abilities in action let’s build a simple TodoApp using Django.
So lets’ get started!
Setting up Python and Django
Before we can start building anything with Python or Django, you must first have both of them installed and set up.
Python Setup
There are two major Python versions: 2.7 and 3.x. However, I’ll be using 2.7 in this tutorial, but it does not really matter what you use. So in case you really want to know the differences between them, the Python wiki has break down the differences. Furthermore, you can install a Virtual Environment which will not be covered in this article, but it’s a good practice.
So if you are using Windows or Linux, you may need to download and install Python manually since they don’t come with it like it used to on Mac. That means if you are using a Mac, you don’t need to install it again unless maybe you’ve tampered or probably removed it while formatting your Mac. However, you can always install it and you should be able to install with ease regardless of the OS
you are using.
Once installed, open the CMD
on Windows, Terminal
on Mac and Linux. Then you will need to use pip to install Django. This is a very good and useful tool that makes it very easy to install and manage packages. All you need to do is:
pip install django
It comes with Python3.x though and of course, you can use it on 2.7 also by downloading and installing it manually. You can head over to the official pip site for more info. You can also install it using easy-install. And if you don’t want to stress yourself, you can go ahead and download Django from the official site. Then unzip it in your python directory, navigate to the folder and install it with the command line using the command below:
cd django_filename
python setup.py install
Note: This might fail if you’ve not set the path environment for python. Ensure you do that.
Once it is installed successfully, you can try importing django in the python environment with command below:
import django
In case you bumped into error you can try checking whether you installed it accurately or maybe you have made a typographical error.
Since everything is set up let’s start building!
Brief Explanation on Django
Django runs on an MVT (Model View Template) system which is different from MVC (Model View Controller) that powers Laravel.
Model: The model in Django sets out the schema for our database. With Django’s ORM, you can declare the fields, field types and any extra data for instance Meta information.
View: The View in Django, is where you set all of your code logic and algorithms. With the View, you can get some results from the database or manipulate some data. The view basically expects a request and a response. The response is usually a HTTP redirect, HTTP error (404), MimeTypes or a call to load a template.
Template: The template in Django is the plain HTML code with Django’s Template Language in it. DTL (Django Template Language) is the language you’ll be using to dynamically write your template. You can do almost everything you’d with python and any other language.
Settings: The settings file in Django, holds all the settings of your web app. It includes the secret key, templates directories, middlewares (security), static files (css,js), database settings and so on.
Url: This does the same thing as routing in Angular or Laravel. It helps to connect the view to a url.
Admin: This deals with how you want to view your models in the django admin.
Creating the TodoApp
Since you now know the basics of django and everything is working fine, we can now start building our todoapp. I’ll show you how to start django project using the django-admin, models, views, crud operations, url routing, DTL (Django Template Language) and some basic settings.
We’ll get started by creating our django project. You can do that by using the command below:
django-admin.py startproject todoapp
The command should have created a todoapp
directory. Use the command cd todoapp
to go into the directory.
After that, you’ll need to create a django app that holds the model and admin stuff. You can do that with the command below:
manage.py startapp todolist
Below is the structure of how our project file structure looks like after setting up everything:
Once you’ve created the project, you can run it and view the url in the browser. You can do that with command below and visit the given url: http://localhost:8100
manage.py runserver 8100
You should be able to see something like below:
We need to update the django settings. So open the settings.py
file in the todoapp directory with any text editor of your choice.
The first thing you need to do is to add the created app “todolist” in the INSTALLED_APPS.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'todolist',
]
This holds the django apps you’ll be using in the django project.
The next is to set up the database. Actually there’s really no need to set up any database since everything is set up already. Django supports a wide range of databases such as SQLite, MySQL, PostgreSQL and Oracle. So since we are only developing it, we’ll be using the default database which is SQLite and the settings.
With the database set up and working fine, we need to add a couple more settings. We’ll be adding a full path to the templates directory that’ll be holding all our html templates. So create a folder called templates
in the project directory or app directory.
Then replace the TEMPLATES list with the below code:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, "templates")],
'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',
],
},
},
]
The code above helps to prepare our template path. The BACKEND
is the templating system you'd like to use and DjangoTemplate
is the default. You can always use other templating system like Jinja
, if you don't like the built-in one. The DIRS
is the path for the templates folder and the BASE_DIR
is the project directory.
We also need to add the static
and static_url
which will handle your css, js and so on files.
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static')
STATICFILES_STORAGE = 'whitenoise.django.GzipManifestStaticFilesStorage'
The PROJECT_ROOT
holds the path for each django apps, STATIC_ROOT
holds the static files
Finally, if you have Dreamweaver installed or any other programs that made changes in css mimetype
, you’ll need to add css mimetype
to enable the css file displays:
import mimetypes
mimetypes.add_type("text/css", ".css", True)
The above code will fix the css mimetype error, if there’s a need for it,
That’s all for our settings.
Model
Now we need to create our model which deals with our database basically. Open the models.py
file in the todolist directory and paste the below code:
from django.utils import timezoneclass Category(models.Model): # The Category table name that inherits models.Model
name = models.CharField(max_length=100) #Like a varchar class Meta:
verbose_name = ("Category")
verbose_name_plural = ("Categories") def __str__(self):
return self.name #name to be shown when calledclass TodoList(models.Model): #Todolist able name that inherits models.Model
title = models.CharField(max_length=250) # a varchar
content = models.TextField(blank=True) # a text field
created = models.DateField(default=timezone.now().strftime("%Y-%m-%d")) # a date
due_date = models.DateField(default=timezone.now().strftime("%Y-%m-%d")) # a date
category = models.ForeignKey(Category, default="general") # a foreignkey class Meta:
ordering = ["-created"] #ordering by the created field def __str__(self):
return self.title #name to be shown when called
The Category
class is for saving our category while the Todolist
is the main database for our todos.
Now that we’ve set up our model, we need to make Django create migrations and migrate for our database. So create migration using the command line with the command below:
manage.py makemigrations
Then use the command below to migrate the migrations
manage.py migrate
After you are done with it, you’ll need to create a super user in order to log in to the admin and so on. In the project directory use the command below:
manage.py createsuperuser
So enter a new username, email and password for django admin. Once you’ve done that, you’ll need to start the server using the default django local web server:
manage.py runserver 8100
Open the 127.0.0.1:8100
url in your browser and should be able to see something similar below:
So we’re now ready to start writing our views, urls and template.
View
Am very sure you know what the View does. So we need to write a view that adds
, deletes
and as well as displays
all the todos. Open and update the views.py
file in the todolist app directory with the code below:
from django.shortcuts import render,redirect
from .models import TodoList, Categorydef index(request): #the index view
todos = TodoList.objects.all() #quering all todos with the object manager
categories = Category.objects.all() #getting all categories with object manager
if request.method == "POST": #checking if the request method is a POST
if "taskAdd" in request.POST: #checking if there is a request to add a todo
title = request.POST["description"] #title
date = str(request.POST["date"]) #date
category = request.POST["category_select"] #category
content = title + " -- " + date + " " + category #content
Todo = TodoList(title=title, content=content, due_date=date, category=Category.objects.get(name=category))
Todo.save() #saving the todo
return redirect("/") #reloading the page if "taskDelete" in request.POST: #checking if there is a request to delete a todo
checkedlist = request.POST["checkedbox"] #checked todos to be deleted
for todo_id in checkedlist:
todo = TodoList.objects.get(id=int(todo_id)) #getting todo id
todo.delete() #deleting todo
return render(request, "index.html", {"todos": todos, "categories":categories})
The above code is a function view that basically saves and deletes todos in our database.
The comments in the above code explain it all, so let proceed to the template.
Template
Since we’ve already created the templates folder, you can go ahead and create an “index.html”
file and paste the below html and DTL
in it.
<!DOCTYPE html>
<html >
<head>
<meta charset="UTF-8">
<title>TodoApp - Create A Todo With Django</title>
{% load static %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}">
</head><body>
<div django-app="TaskManager">
<div class="container">
<div class="content">
<h1>TodoApp</h1>
<p class="tagline">a Django todo app</p>
<form action="" method="post">
{% csrf_token %} <!-- csrf token for basic security -->
<div class="inputContainer">
<input type="text" id="description" class="taskName" placeholder="What do you need to do?" name="description" required>
<label for="description">Description</label>
</div>
<div class="inputContainer half last">
<i class="fa fa-caret-down selectArrow"></i>
<select id="category" class="taskCategory" name="category_select">
<option class="disabled" value="">Choose a category</option>
{% for category in categories %}
<option class="" value="{{ category.name }}" name="{{ category.name }}">{{ category.name }}</option>
{% endfor %}
</select>
<label for="category">Category</label>
</div>
<div class="inputContainer half last right">
<input type="date" id="dueDate" class="taskDate" name="date">
<label for="dueDate">Due Date</label>
</div>
<div class="row">
<button class="taskAdd" name="taskAdd" type="submit"><i class="fa fa-plus icon"></i>Add task</button>
<button class="taskDelete" name="taskDelete" formnovalidate="" type="submit" onclick="$('input#sublist').click();"><i class="fa fa-trash-o icon"></i>Delete Tasks</button>
</div> <ul class="taskList">
{% for todo in todos %} <!-- django template lang - for loop -->
<li class="taskItem">
<input type="checkbox" class="taskCheckbox" name="checkedbox" id="{{ todo.id }}" value="{{ todo.id }}">
<label for="{{ todo.id }}"><span class="complete-">{{ todo.title }}</span></label>
<span class="category-{{ todo.category }}">{{ todo.category }}</span>
<strong class="taskDate"><i class="fa fa-calendar"></i>{{ todo.created }} - {{ todo.due_date }}</strong>
</li>
{% endfor %} </ul><!-- taskList -->
</form>
</div><!-- content --> </div><!-- container -->
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</body>
</html>
The above code is an html/css code with django template language which displays and styles our app frontend.
In case you don’t want to use the template, you can always build yours if you know html/css and just implement the django variables and tags like I did. Or maybe you can just tweak the css. In the template above, you’ll notice some strange text enclosed in {{}}
and {%%}
. The former is django variable
while the latter is django template tags.
You’ll also need to add some css in order to style it. I presume, you’ve set up the static
file directory in the settings.py
. Just create a static
folder in the todolist app directory then you can create the css
, fonts
and js
files you’ll need.
I prefer putting my css files in a css folder; so you can create a css folder, then create a style.css
file and paste the below stylesheet:
/* basic reset */
*, *:before, *:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
/* app */
html {
font-size: 100%;
}
body {
background: #b1f6cb;
font-family: 'Open Sans',sans-serif;
}
/* super basic grid structure */
.container {
width: 600px;
margin: 0 auto;
background: #ffffff;
padding: 20px 0;
-webkit-box-shadow: 0 0 2px rgba(0,0,0,0.2);
box-shadow: 0 0 2px rgba(0,0,0,0.2);
}
.row {
display: block;
padding: 10px;
text-align: center;
width: 100%;
clear: both;
overflow: hidden;
}
.half {
width: 50%;
float: left;
}
.content {
background: #fff;
}
/* logo */
h1 {
font-family: 'Rokkitt', sans-serif;
color: #666;
text-align: center;
font-weight: 400;
margin: 0;
}
.tagline {
margin-top: -10px;
text-align: center;
padding: 5px 20px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
color: #777;
}
/* inputs */
.inputContainer {
height: 60px;
border-top: 1px solid #e5e5e5;
position: relative;
overflow: hidden;
}
.inputContainer.last {
border-bottom: 1px solid #e5e5e5;
margin-bottom: 20px;
}
.inputContainer.half.last.right {
border-left: 1px solid #efefef;
}
input[type="date"], input[type="text"], select {
height: 100%;
width: 100%;
padding: 0 20px;
position: absolute;
top: 0;
vertical-align: middle;
display: inline-block;
border: none;
border-radius: none;
font-size: 13px;
color: #777;
margin: 0;
font-family: 'Open Sans', sans-serif;
font-weight: 600;
letter-spacing: 0.5px;
-webkit-transition: background 0.3s;
transition: background 0.3s;
}
input[type="date"] {
cursor: pointer;
}
input[type="date"]:focus, input[type="text"]:focus, select:focus {
outline: none;
background: #ecf0f1;
}
::-webkit-input-placeholder {
color: lightgrey;
font-weight: normal;
-webkit-transition: all 0.3s;
transition: all 0.3s;
}
::-moz-placeholder {
color: lightgrey;
font-weight: normal;
transition: all 0.3s;
}
::-ms-input-placeholder {
color: lightgrey;
font-weight: normal;
transition: all 0.3s;
}
input:-moz-placeholder {
color: lightgrey;
font-weight: normal;
transition: all 0.3s;
}
input:focus::-webkit-input-placeholder {
color: #95a5a6;
font-weight: bold;
}
input:focus::-moz-input-placeholder {
color: #95a5a6;
font-weight: bold;
}
.inputContainer label {
padding: 5px 20px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
color: #777;
display: block;
position: absolute;
}
button {
font-family: 'Open Sans', sans-serif;
background: transparent;
border-radius: 2px;
border: none;
outline: none;
height: 50px;
font-size: 14px;
color: #fff;
cursor: pointer;
text-transform: uppercase;
position: relative;
-webkit-transition: all 0.3s;
transition: all 0.3s;
padding-left: 30px;
padding-right: 15px;
}
.icon {
position: absolute;
top: 30%;
left: 10px;
font-size: 20px;
}
.taskAdd {
background: #444;
padding-left: 31px;
}
.taskAdd:hover {
background: #303030;
}
.taskDelete {
background: #e74c3c;
padding-left: 30px;
}
.taskDelete:hover {
background: #c0392b;
}
/* task styles */
.taskList {
list-style: none;
padding: 0 20px;
}
.taskItem {
border-top: 1px solid #e5e5e5;
padding: 15px 0;
color: #777;
font-weight: 600;
font-size: 14px;
letter-spacing: 0.5px;
}
.taskList .taskItem:nth-child(even) {
background: #fcfcfc;
}
.taskCheckbox {
margin-right: 1em;
}
.complete-true {
text-decoration: line-through;
color: #bebebe;
}
.taskList .taskDate {
color: #95a5a6;
font-size: 10px;
font-weight: bold;
text-transform: uppercase;
display: block;
margin-left: 41px;
}
.fa-calendar {
margin-right: 10px;
font-size: 16px;
}
[class*='category-'] {
display: inline-block;
font-size: 10px;
background: #444;
vertical-align: middle;
color: #fff;
padding: 10px;
width: 75px;
text-align: center;
border-radius: 2px;
float: right;
font-weight: normal;
text-transform: uppercase;
margin-right: 20px;
}
.category- {
background: transparent;
}
.category-Personal {
background: #2980b9;
}
.category-Work {
background: #8e44ad;
}
.category-School {
background: #f39c12;
}
.category-Cleaning {
background: #16a085;
}
.category-Other {
background: #d35400;
}
footer {
text-align: center;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
color: #777;
}
footer a {
color: #f39c12;
}
/* custom checkboxes */
.taskCheckbox {
-webkit-appearance: none;
appearance: none;
-webkit-transition: all 0.3s;
transition: all 0.3s;
display: inline-block;
cursor: pointer;
width: 19px;
height: 19px;
vertical-align: middle;
}
.taskCheckbox:focus {
outline: none;
}
.taskCheckbox:before, .taskCheckbox:checked:before {
font-family: 'FontAwesome';
color: #444;
font-size: 20px;
-webkit-transition: all 0.3s;
transition: all 0.3s;
}
.taskCheckbox:before {
content: '\f096';
}
.taskCheckbox:checked:before {
content: '\f14a';
color: #16a085;
}
/* custom select menu */
.taskCategory {
-webkit-appearance: none;
appearance: none;
cursor: pointer;
padding-left: 16.5px; /*specific positioning due to difficult behavior of select element*/
background: #fff;
}
.selectArrow {
position: absolute;
z-index: 10;
top: 35%;
right: 0;
margin-right: 20px;
color: #777;
pointer-events: none;
}
.taskCategory option {
background: #fff;
border: none;
outline: none;
padding: 0 100px;
}
This is just a simple CSSstylesheet
which gives our app a nice ui.
Also, if you don’t want to stress yourself in setting up the static folder and so on, you can simply use the stylesheet as inline in the html file. All you need to do is to put the stylesheet above enclosed with the style tag before the head close tag like below:
<style type=”text/css”> “the stylesheet” </style>
</head>
That’s all. Easy, isn’t it?
Url
The last thing to do now is to set up our url. So simply open the urls.py
in the project root directory
and update it with the below code:
from django.conf.urls import url
from django.contrib import admin
from todolist.views import indexurlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', index, name="TodoList"),
]
This handles our app url routing
Admin
Another thing is, we need to update the admin.py
file in the todolist app directory. This will enable us to create categories from django admin manually. So head over to the file and paste the code below:
from . import modelsclass TodoListAdmin(admin.ModelAdmin):
list_display = ("title", "created", "due_date")class CategoryAdmin(admin.ModelAdmin):
list_display = ("name",)admin.site.register(models.TodoList, TodoListAdmin)
admin.site.register(models.Category, CategoryAdmin)
That’s all for the coding and settings.
Run it using the same command: manage.py runserver 8100
You should be able to see something like below:
Creating Categories
To create categories, head over to the http://localhost:8100/admin and login with the details you used to create a new account earlier.
After logging in, you should be able to see the django admin interface, Categories and TodoList (table).
Click on the Categories
, select Add Category
, enter any category you want and click on save. You can consider creating basic categories like below
- General
- Personal
- Work
- School
- Cleaning
- Other
Note: Each of the categories above, already have it’s own defined color in our css. The category label changes color depending on what label you chose.
Once you are done with that, let’s see how we can add and delete todos.
Adding and Deleting Todos
Go back to the index page which should be http://localhost:8100.
You should be able to see something like below:
To add todo, enter the description, select category, select the due date and click on Add Task
.
If you’d like to delete any todo, just click on it and select DELETE TASK
Conclusion
I presume you’ve already grasp the basics of building web apps with django with the things we’ve covered so far. I recommend you to read this article to really know more about python and django.
You can checkout the code on the github repo: https://github.com/CITGuru/todoapp
Resources
Build Your First Python And Django Application
Note: This is an old article of mine left unpublished.
Clap, if you enjoyed this article.