Django CRUD Application – Todo App – Tutorial

In this post we will build Todo Application using Django, before going thru this application, you should have basic knowledge of Django. If you want to review basics, please go thru following posts.

Let us create project

$ django-admin startproject mysite06
$ cd mysite06
/mysite06$ ls -la
total 16
drwxr-xr-x 3 conquistadorjd conquistadorjd 4096 Dec 31 10:15 .
drwxrwxrwx 15 conquistadorjd conquistadorjd 4096 Dec 31 10:15 ..
-rwxr-xr-x 1 conquistadorjd conquistadorjd 628 Dec 31 10:15 manage.py
drwxr-xr-x 2 conquistadorjd conquistadorjd 4096 Dec 31 10:15 mysite06
/mysite06$ python3 manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).

You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.

December 31, 2019 - 04:45:40
Django version 3.0.1, using settings 'mysite06.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[31/Dec/2019 04:45:44] "GET / HTTP/1.1" 200 16351
[31/Dec/2019 04:45:44] "GET /static/admin/css/fonts.css HTTP/1.1" 200 423
[31/Dec/2019 04:45:45] "GET /static/admin/fonts/Roboto-Regular-webfont.woff HTTP/1.1" 200 85876
[31/Dec/2019 04:45:45] "GET /static/admin/fonts/Roboto-Light-webfont.woff HTTP/1.1" 200 85692
[31/Dec/2019 04:45:45] "GET /static/admin/fonts/Roboto-Bold-webfont.woff HTTP/1.1" 200 86184
Not Found: /favicon.ico
[31/Dec/2019 04:45:45] "GET /favicon.ico HTTP/1.1" 404 1974

Now our basic project  is running

Now we need to create an app now.

python3 manage.py startapp todo

by default urls.py is not created in app, we need to create todo/urls.py manually.

from django.urls import path
from . import views

app_name = 'todo'
urlpatterns = [
    path('first/', views.index, name='index'),  
]

To have application urls accessible from main project, we need to add this urls.py with main project

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

urlpatterns = [
    path('todo/', include('todo.urls')),# Add this line
    path('admin/', admin.site.urls),
]

before doing further changes let us create sample view in todo/views.py we will edit this file later for details but we need to create this view to avoid any error while running intermediate command.


from django.shortcuts import render, get_object_or_404, redirect

def index(request):
    context = "temp"
    return render(request, 'todo/index.html',{'context':context})  

 

Changes  in settings.py file

Add application in settings file

INSTALLED_APPS = [
    'todo.apps.TodoConfig', # add this statement
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

Here instead of ‘todo.TodoConfig’, even if you add ‘todo’ it will work but official method is to use ‘todo.TodoConfig’ so lets stick to that. You might wonder where ‘todo.TodoConfig’ is coming from. Just looke at app.py under todo app directory.

Now let us define templates directory as below

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR + '/templates/', # add this line
        ],
        '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',
            ],
        },
    },
]

and finally define static directory at the end of settings.py file.

STATICFILES_DIRS = [
    os.path.join(BASE_DIR, "static"),
]

We need to manually create static and templates directories. Directory structure created looks as below:

├── static
│   ├── css
│   ├── img
│   └── js
├── templates
│   └── todo

Creating model

Let us first create todoapp database and database user

$sudo su - postgres
[sudo] password for conquistadorjd: 
postgres@inspiron-3542:~$ psql
psql (10.10 (Ubuntu 10.10-0ubuntu0.18.04.1))
Type "help" for help.
postgres=# CREATE DATABASE todoapp;
CREATE DATABASE
postgres=# CREATE USER todoappuser WITH PASSWORD 'todoappsuser';  
CREATE ROLE
postgres=# ALTER ROLE todoappuser SET client_encoding TO 'utf8';
ALTER ROLE
postgres=# ALTER ROLE todoappuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE
postgres=# ALTER ROLE todoappuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE
postgres=# ALTER ROLE todoappuser SET timezone TO 'UTC';
ALTER ROLE
postgres=# GRANT ALL PRIVILEGES ON DATABASE todoapp TO todoappuser;
GRANT
postgres=# \q
postgres@inspiron-3542:~$ exit
logout

Now we need to configure this database in our application


DATABASES = {
    'default': {
        # 'ENGINE': 'django.db.backends.sqlite3',
        # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'todoapp',
        'USER': 'todoappuser',
        'PASSWORD': 'todoappuser',
        'HOST': 'localhost',
        'PORT': '',              
    }
}

Once this is done, we need to run following two commands


$ python3 manage.py makemigrations todo
Migrations for 'todo':
  todo/migrations/0001_initial.py
    - Create model Task
$ python3 manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions, todo
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying sessions.0001_initial... OK
  Applying todo.0001_initial... OK

Let us look at database structure now


postgres=# \connect todoapp
You are now connected to database "todoapp" as user "postgres".
todoapp=# \dt
                     List of relations
 Schema |            Name            | Type  |    Owner    
--------+----------------------------+-------+-------------
 public | auth_group                 | table | todoappuser
 public | auth_group_permissions     | table | todoappuser
 public | auth_permission            | table | todoappuser
 public | auth_user                  | table | todoappuser
 public | auth_user_groups           | table | todoappuser
 public | auth_user_user_permissions | table | todoappuser
 public | django_admin_log           | table | todoappuser
 public | django_content_type        | table | todoappuser
 public | django_migrations          | table | todoappuser
 public | django_session             | table | todoappuser
 public | todo_task                  | table | todoappuser
(11 rows)

As you can see tables are now created in database.

Let us have this model accessible from admin and create some dummy data for development and unit testing.


from django.contrib import admin

from .models import Task

admin.site.register(Task)

Now create a admin user using following command

python3 manage.py createsuperuser

Let us run the server and login to admin from http://127.0.0.1:8000/admin and create some dummy data.

CRUD Application

Now our main activity of CRUD appliaction starts. You need to create urls for create, read,update and delete application along with matching views and templates.

url view Template Remark
/ index index.html
/newtask
newtask
newtask.html
Create new task
/<id> detail detail.html Task Details
/edit/<id> edit newtask.html Edit Task Details
/delete/<id> delete NA

(Once you click on delete, it will delete task and show index.html)

delete task

Here is code for todo/urls.py


from django.urls import path
from . import views

app_name = 'todo'
urlpatterns = [
    path('', views.index, name='index'),  
    path('/', views.detail, name='detail'),
    path('newtask/', views.newtask, name='newtask'),
    path('edit/', views.edit, name='edit'),
    path('delete/', views.delete, name='delete'),
]

todo/Views.py


from django.shortcuts import render, get_object_or_404, redirect
from .models import Task
from .forms import TaskForm

# Create your views here.

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

def detail(request, task_id):
    task = get_object_or_404(Task, pk=task_id)
    return render(request, 'todo/detail.html', {'task': task})    

def newtask(request):
    form = TaskForm(request.POST or None)
    if form.is_valid():
        form.save()
        return redirect("/todo/")
    return render(request, 'todo/newtask.html', {'form':form})

def edit(request, task_id):
    task = get_object_or_404(Task, pk=task_id)
    print("**", task.title)
    form = TaskForm(request.POST or None, instance=task)
    if form.is_valid():
        form.save()
        return redirect("/todo/")
    return render(request, 'todo/newtask.html', {'form':form})


def delete(request, task_id):
    # print(request.method )  
    task = Task.objects.get(id=task_id)  
    task.delete()  
    return redirect("/todo/") 

index.html

{% extends "components/base.html" %}

{% block content %}

<div class="container-fluid">

{% if context %}

<tableclass="table">

<thead>

<tr>

<thscope="col">Title</th>

<thscope="col">Created Date</th>

<thscope="col">Status</th>

<thscope="col">Completed Date</th>

<thscope="col">Action</th>

</tr>

</thead>

<tbody>

{% for task in context %}

<tr>

<td><li><ahref="{% url 'todo:detail' task.id %}">{{ task.title }}</a></li></td>

<td>{{ task.created_date }}</td>

<td>{{ task.completed }}</td>

<td>{{ task.completed_date }}</td>

<td>

<ahref="/todo/edit/{{ task.id }}"><spanclass="glyphicon glyphicon-pencil">Edit</span></a>

<ahref="/todo/delete/{{ task.id }}">Delete</a>

</td>

</tr>

{% endfor %}

</tbody>

</table>

{% else %}

<p>No tasks are available.</p>

{% endif %}

<center><ahref="/todo/newtask"class="btn btn-primary">Add New Record</a></center>

</div>

{% endblock %}

newtask.html

{% extends "components/base.html" %}

{% block content %}

<h1>Edit</h1>

<form method="post">{% csrf_token %}

{{ form.as_p }}

<inputtype="submit"value="Submit"/>

</form

{% endblock %}

details.html

{% extends "components/base.html" %}

{% block content %}

<h5>Title : {{ task.title }}</h5>

<p>Description : {{ task.description }}</p>

<p>created_date : {{ task.created_date }}</p>

<p>due_date : {{ task.due_date }}</p>

<p>completed : {{ task.completed }}</p>

<p>note : {{ task.note }}</p>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<ahref="/todo/edit/{{ task.id }}"><spanclass="glyphicon glyphicon-pencil">Edit</span></a>

{% endblock %}

This application is available on Github for your reference.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.