Build a Project Management Application in Django

Introduction to Django, Virtual Environments, Templates, Url Patterns, Databases and the Django ORM

Esther Vaati
Level Up Coding
Published in
23 min readMay 26, 2023

--

What is Django

Django is the most popular Python framework, While other Python frameworks like Flask leave a lot of the functionality and structure to the developer. Django comes with everything needed to organize your application’s design structure; hence, as a developer, you can concentrate on the code without worrying about organizing your project structure.

Django also has a very active and friendly community .Some community resources are the Django forum and django-users

Prerequisites

  • You should have a basic knowledge of Python, HTML, Bootstrap and CSS and a general understanding how web pages work.

Learning Objectives

In this guide, we will create a Project management Django application. We will split it into three parts. In part 1 of this guide, we will cover the following concepts:

  • Virtual environments
  • The structure of your django application.
  • How to define views in your django application.
  • How to define the data structure using models
  • Models and model relationships.
  • How to map URLS to views using URL patterns.
  • How to handle user requests using views
  • How to render HTML user interface using django templates.
  • How to use the django ORM (Object-Relational Mapping) for database management.

Source Code

Download the three part PDF guide .

Virtual environments

A virtual environment is a folder that allows you to install isolated Python packages for every project.For example, if you have two Django projects, each requiring a different version of Django, using a virtual environment for each project allows you to install and use the specific Django version needed for that project. This way, you can maintain compatibility and avoid conflicts between the Django versions used in different projects.

Your First Django Project

Since we have covered why we need a virtual environment, we will not install Django locally in our operating system but instead in a virtual environment. Let’s create a folder to house our django application and the virtual environment.

mkdir django_projects

Next, create a virtual environment using the venv module. The venv module allows you to create and manage virtual environments by isolating packages and dependencies.

cd django_projects
python -m venv myenv

The python -m venv myenv command creates a new virtual environment named myenv in the django_projects directory.

Activate the virtual environment.

source myenv/bin/activate

If you are using Windows, the command for activating your virtual environment will be:

myenv\Scripts\activate

Install Django in the virtual environment.

pip install django

When you issue the above command, the latest version of Django will be installed, and you if all goes well, you should see something like this:

Collecting django
Using cached Django-4.2.1-py3-none-any.whl (8.0 MB)
Collecting sqlparse>=0.3.1
Using cached sqlparse-0.4.4-py3-none-any.whl (41 kB)
Collecting asgiref<4,>=3.6.0
Using cached asgiref-3.6.0-py3-none-any.whl (23 kB)
Installing collected packages: sqlparse, asgiref, django
Successfully installed asgiref-3.6.0 django-4.2.1 sqlparse-0.4.4

If you wish to install a specific version of django, for example, Django 4.0, specify the version while installing as shown.

pip install django==4.0

To create a django project, Django comes with the django-admin command, which allows you to perform administrative tasks. The command looks like this.

django-admin <command> [options]

To create a new django project, issue the following command in your terminal.

django-admin startproject django_work

Where django_work is the name of the django project, to ensure there are no conflicts, you should not have hyphens in the name of your django project or use inbuilt function names; for example, you can’t name your project django or test.

Django uses a requirements.txt file to list all the modules needed for the django project. A requirements.txt file allows the project to be reproduced on another machine. It’s also essential for team collaboration. The requirements.txt file should be in the project root directory.

Issue the pip freeze command to see all the modules installed in your virtual environment.

pip freeze

Output:

asgiref==3.6.0
Django==4.2.1
sqlparse==0.4.4

To generate a requirements.txt file, go to your project directory and issue the following command.

pip freeze > requirements.txt

The above command will write all the installed packages and their versions to a requirement.txt file.

Your project directory now looks like this:

.
├── django_work
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
└── requirements.txt
1 directory, 7 files

Each of the files generated by django serve a specific purpose.

__init__.py -

This is an empty file that tells us that the directory django_work is a package.

asgi.py

This file contains the configurations necessary to deploy your django application with ASGI. ASGI (Asynchronous Server Gateway Interface), a successor to WSGI, handles asynchronous requests between your Djjango application and the server.

settings.py

This file contains configurations for your django application, such as database settings, middleware settings, static file settings, template settings, installed _apps, and other configurations.

urls.py

This file contains the URL dispatcher settings for your django application. The file now looks like this:

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

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

From the file above, the available paths for our django application if it were running locally are:

https://127.0.0.1:8000/admin

wsgi.py

This file contains the configurations necessary to deploy your django application with WSGI (Web Server Gateway Interface).

manage.py

manage.py is another tool provided by django that allows you to perform django tasks such as running the server, creating django apps, running tests, e.t.c

Django development server

You have just created your first Django application. To see it running locally, issue the following command.

python manage.py runserver

You should see something like this on the terminal.

Watching for file changes with StatReloader
Performing system checks…
System check identified no issues (0 silenced).
You have 18 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.
May 23, 2023–12:50:58
Django version 4.2.1, using settings 'django_work.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Don’t worry about the migrations error, which tells you you have unapplied migrations; we will cover migrations later.

The django development server is now running at http://127.0.0.1:8000/. Using your web browser, navigate to http://127.0.0.1:8000/ and see the django default page.

This shows that your django application is running as expected. This is a development server and, therefore, not ideal for use in production. The server also watches for any changes you make in your code, so there is no need to restart the server every time you make changes.

By default, the server will run at port 8000; however, if you wish to change the port, you can specify it when running the development server.

python manage.py runserver 8001

Django Apps

Django allows you to separate different components of your project using apps. For example, we will have projects and a users app. We use the python manage.py startapp command to create a new app, as shown below.

python manage.py startapp <app-name>

Where app-name is the app’s name, let’s go ahead and create a projects app. Go to the root directory of your django project and issue the following command.

python manage.py startapp projects

Django will create a projects folder and generate starter files in the directory. The project tree should now look like this:

.
├── db.sqlite3
├── django_work
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
├── projects
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── requirements.txt

Your django project, however, does not know about the projects app, so we need to add it to the list of installed_apps in the settings.py file. Open the settings.py file and add it as shown below.

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'projects, # add here.
]

Django apps are reusable and movable, so they can be transferred and integrated into another django project.

Your first django view

Django views are used to serve and pass web requests to the server. For example, when browsing a web page, the webpage is served by a view function. Django views contain all the logic needed to get requests from the user and return the appropriate response. Django views are placed in the views.py file.

Some views in django will fetch data from a database or an API; you can have views that return static data (data that doesn’t change). For example, let’s write a view that shows the current date and time. Open projects/views.py and add the following code.

from django.shortcuts import render
from django.http import HttpResponse
import datetime

# Create your views here.
def current_time(request):
current_date_time = datetime.datetime.now()
return HttpResponse(f"The Current time: {current_date_time}")

In the code above, First, we import the datetime module and HttpResponse class from Django’s http module. Then we define a function view called current_time which takes a request object as a parameter; inside the current_datetime function, we then use datetime.datetime.now() to get the current date and time.

Finally, we return an HTTP response containing the current date and time. To serve this view to the browser, we map the view to a Url. Django uses url_patterns to match views to urls; these settings are found in urls.py. Every app will have its own urls.py file. However, let’s use the root urls.py file to serve our view. Open urls.py and map the current_time view to a url.

from django.contrib import admin
from django.urls import path
from projects.views import current_time

urlpatterns = [
path('admin/', admin.site.urls),
path('time/', current_time),
]

In the code above, we import the current_time function from the projects/views.py file. If you are not familiar with imports, visit the Python documentation.

Let’s run the server and see it in action.

python manage.py runserver

Now navigate to http://127.0.0.1:8000/time/ and see the current datetime rendered on the browser.

Templates and Urls

We have seen how to serve a simple text, but our application will grow, and we will need to serve our data through templates. Template configurations in django are defined in the settings.py file, as shown below.

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',
],
},
},
]

The APP_DIRS’: True, means that django will look for templates in the app’s directories by default. So, for example, for our project’s app, we need to create a templates folder. By convention, when you create a templates folder, you should also create another folder inside with the same name as the app. All the template files will then be placed in the inner projects folder. Let’s create our templates directory and add a projects.html page. Our projects folder structure should look now like this:

├── admin.py
├── apps.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── templates
│ └── projects.html
├── tests.py
└── views.py
3 directories, 8 files

Now open projects.html and add the following code.

<!DOCTYPE html>
<html>
<head>
<title>Projects</title>
</head>
<body>
<h1>Getting started with django</h1>
</body>
</html>

Let’s write a view to serve the projects.html template; update the projects/views.py file as follows:

from django.shortcuts import render

# Create your views here.
def projects(request):
return render(request, 'projects/projects.html')

In the code above, we first import the render function from django.shortcuts. The render function is used to render a django template. We then define a projects view which takes a request object as a parameter. We then use the render function to render the project.html template.

As we mentioned, each django app should have its urls.py file; create a new file, urls.py, in the projects folder.

.
├── admin.py
├── apps.py
├── __init__.py
├── migrations
│ └── __init__.py
├── models.py
├── templates
│ └── projects
│ └── projects.html
├── tests.py
├── urls.py
└── views.py
3 directories, 9 files

Map the projects function to a url. Open the project/urls.py file and add the following code.

from django.urls import path
from . import views

urlpatterns = [
path('projects', views.projects, name = 'projects'),

]

Your django project needs to know about the project urls. Open the root urls.py file and include the projects.urls so that they are available for requests.

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

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

]

Run the development server.

python manage.py runserver

Now the server will render the content inside the projects.html page, and you should see the page( http://127.0.0.1:8000/projects )in your web browser.

Template files can also be placed in the root directory of your django project. With this configuration, we need to make a few changes to the template’s settings configuration. Open the settings.py file and set DIRS’: [BASE_DIR/ “templates”]

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [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',
],
},
},
]

With the above settings, we are telling django to also look for templates in a templates folder in the base_directory.

Let’s add the templates folder at the root of our django project. In the root templates folder, add the index.html page.

.
├── django_work
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── manage.py
├── projects
│ ├── admin.py
│ ├── apps.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── templates
│ │ └── projects
│ │ └── projects.html
│ ├── tests.py
│ └── views.py
├── requirements.txt
└── templates
└── index.html

Add the following code in the index.html file.

<!DOCTYPE html>
<html>
<head>
<title>Work</title>
</head>
<body>
<h1>Getting started with django</h1>
<h2>This is the root template</h2>
</body>
</html>

Now let’s add a view function to serve the new template. Our projects/view.py file now looks like this:

from django.shortcuts import render
# Create your views here.
def home(request):
return render(request, 'index.html')

def projects(request):
return render(request, 'projects/projects.html')

Map the home view to a url.

from django.urls import path
from . import views

urlpatterns = [
path('projects', views.projects, name = 'projects'),
path('home', views.home, name = 'home'),

]

The home page now looks like this:

Urls patterns

We have seen how to map views to urls; for example, let’s break down the url below.

path('home', views.home, name = 'home'),

When the django development server is running, ‘home’ is the url pattern that will serve the contents of the index.html page.

  • views.home: This view function renders the index.html page.
  • name = ‘home’ is a name assigned to the url pattern. Names in urls help uniquely identify URL patterns in django. They also make it easy to add links in templates. For example, if we needed to add a link to the home page anywhere in the projects.html template, it would be referenced like this:
<a href=”{% url ‘home’ %}”>Home</a>

Django template engine

The django template engine is a powerful language that allows you to pass variables to templates, write conditional logic and loops, and add urls in templates.

To pass variables from the view to the template, django uses a context. A context is a dictionary containing data to be passed to a template.

Go to projects/views.py, add a context dictionary, and pass it as a request to the template.

def projects(request):
context = {'project1': 'My first project'}
return render(request, 'projects/projects.html',context)

Within the projects.html template, you can access the project1 variable using double curly brackets, as shown below.

<!DOCTYPE html>
<html>
<head>
<title>Projects</title>
</head>
<body>
<h1>Getting started with django</h1>
<p>{{project1}}</p>
</body>
</html>

Double curly brackets are template tags used for rendering variables or expressions.

Now if you refresh the page,http://127.0.0.1:8000/projects you notice that the page has been updated.

You can apply django filters or any other Python expression within the tags. For example, you can apply the upper filter to make the variable uppercase. The pipe(|) character is used to apply the filter.

<p>{{project1|upper}}</p>

Loops and conditionals in templates

Data obtained from the database is usually in dictionary and list formats. To pass this data to a template, you would iterate with for loops or if statements and render it to the templates. Suppose you had a list projects where each project has an id and a title. You would pass the projects as a context as shown below.

def projects(request):
projects = [
{
'id':1,
'title':"PROJECT A"
},
{
'id':2,
'title':"PROJECT B"
}]
context = {'projects': 'projects'}
return render(request, 'projects/projects.html',context)

To render each project instance details, you would then write a for loop that looks like this:

<!DOCTYPE html>
<html>
<body>
<h1>My Projects</h1>
{% for project in projects%}
<p>{{project.id}}</p>
<p> {{project.title}}</p>
{% endfor%}
</body>
</html>

As you can see above, the template tags used for the for loop differ from those used to display the variables. django template engine uses the {% %} tags for templates and conditional statements. Just like a normal for loop, the {% for %} and {% endfor %} enclose the code to be executed .

Template Inheritance

Template inheritance is another powerful tool provided by django. Template inheritance allows you to build a base template that contains the most commonly repeated elements of your application, such as the header, navbar, footer, or any other common page elements.

The base template contains blocks that serve as placeholders for child templates. Other templates can then inherit the base template.

For example, let’s create a base.html template in the root templates folder and add the blocks that will make up a standard webpage

<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<link rel="stylesheet" href=" " />
<title>Work Projects</title>
</head>
<body>

{% block content %}
{% endblock content %}
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
</body>
</html>

In the code above, we define a basic structure of a web page with a title and a body and add Bootstrap CSS and javascript files since we will use Bootstrap to style our application. As you can see, the body contains a block content where child elements will be inserted.

The projects.html file can inherit the base template and fill the content block as shown below.

{% extends "base.html" %}
{% block content %}
<h1>My Projects</h1>
{% for project in projects%}
<p>{{project.id}}</p>
<p> {{project.title}}</p>
{% endfor%}
{% endblock %}

The {% extends “base.html” %}tag means that the above template inherits all the base template’s contents and replaces the content block with the contents of the child template.

Django also has the {% include %} tag that is used to include the contents of another template in the current template; update the body of the base.html to include a navbar.html template which will have the contents of the nav bar.

<body>
{% include 'navbar.html' %}
{% block content %}
{% endblock content %}
<! - the rest of the content here →
</body>

Create the navbar.html file in the same folder as the base.html template and add the following code:

<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="{% url 'home' %}">Home</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="">Projects </a>
</li>
<li class="nav-item">
<a class="nav-link" href="">Tasks</a>
</li>
<li class="nav-item">
<a class="nav-link btn btn-primary" href="">Create Task</a>
</li>
<br>
<li class="nav-item">
<a class="nav-link btn btn-primary" href="">Create Project</a>
</li>
</ul>
</div>
<a class="btn btn-primary" href="" role="button">Sign Up</a>
<a class="btn btn-primary" href="" role="button">Login</a>
</div>
</div>
</div>
</nav>

Static files

Static files in django include images, CSS, and javascript files. The static settings are configured in the settings.py file.

STATIC_URL = ‘static/’

The settings defined above specify the base URL for serving static files. This organization is similar to the templates. Storing static files within your django apps’ static folder helps maintain a clean and modular structure.

Within your projects app, add a static folder. Inside the static folder, add a folder that contains a main.css file.

.static
└── css
└── main.css

Add the load static tag and a link to the main.css in your templates.

{% load static % }
<link rel=”stylesheet” href=”{% static ‘css/main.css’ %}” type="text/css" />

{% load static % } is the tag used to load static files within a template. The {% static ‘css/main.css’ %} references the main.css file.

Django also provides a way to collect all the static files in one place using the collectstatic command. Once you run the collectstatic command, Django searches for static files within each app; once the static files are collected, Django copies them to the directory specified in STATIC_ROOT in the settings.py file.

Collecting static files in a central place makes it easy to deploy them in a production environment.

Models and Database

Almost every application needs to manage data. Databases are used to store and process small to large amounts of data. A database consists of tables; a database table is a collection of rows and columns.

For example, in the image above, we have a database table consisting of 2 tables; each has rows and columns. Each row represents a record or an instance of data, while a column represents an attribute of the data. The tables are also connected through relationships; for example, each task instance is linked to a project instance.

Django provides an inbuilt support for the following databases:

  • PostgreSQL
  • MariaDB
  • MySQL
  • Oracle
  • SQLite

Django comes with a default SQLite configuration which is perfect for development purposes. This configuration is found in the settings.py file.

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}

SQLite is a lightweight database option that makes starting with database-backed web applications easy without additional setup or configuration.

Models

In django, models are used to represent objects. Objects have attributes, and these attributes determine the design of the database Schema. In django, these tables are represented by classes.
In django, we would represent our Project and Task tables using model classes as shown below.

Django model design

In the model classes above, CharField,DateField,TextField and ForeignKey are field types. Field types determine what kind of data is stored in the database.

  • A primary key is a unique number that identifies each instance of a model.
  • CharField represents a string, and it’s used to store small to medium-length strings.
  • TextField represents a large text, and it’s used to store large amounts of text.
  • DateField represents date and is used to store date-related information
  • ForeignKey represents a relationship between the task and Project model; This means that each task can be associated with a single project, while a single project can have multiple tasks.

Before designing any database schema, you should ask yourself the following questions

  • What kind of data do you want to represent
  • Is the data important

These questions will help you better understand and represent data that meets the needs of your application.

Let’s add a Project model, open projects/models.py, and add the following code.

# projects/models.py

from django.db import models
# Create your models here.

class Project(models.Model):
name = models.CharField(max_length=200,null=True,blank=True)
description = models.TextField(null=True,blank=True)
date_created = models.DateTimeField(auto_now_add=True,null=True,blank=True)

Some field types take in some arguments; for example, CharField requires the max_length, which specifies the number of characters of the field. CharField can also take in additional arguments, such as null and blank.

first_name = models.CharField(max_length= 100, null = False, blank = False)
  • Null = False means that the field will not take any null value, and the reverse is True
  • blank=False implies that the field can’t be blank when working with forms, i.e., it should not be empty

The Project model is now complete. By default, Django will give each model a primary key.

Migrations

After designing our first model, the next thing django requires us to do is to run migrations. Migrations are a way of instructing the database on how our structure will look.

Migrations in Django provide the rules of writing to the database. The two migration commands are:

python manage.py makemigrations
python manage.py migrate

The makemigrations command creates a migrations file that provides the rules by which the database tables are created. Let’s issue this command in the terminal

python manage.py makemigrations

If no error occurs, you should see something like this:

Migrations for 'projects':
projects/migrations/0001_initial.py
- Create model Project

As you can see above, a file called 0001.initial.py has been created in the migrations folder. This file contains the structure of the database table.

The makemigrations folder only creates a blueprint of how our Project table will look; let’s issue the migrate command, which will create the database tables.

python manage.py migrate

You should see something like this:

Operations to perform:
Apply all migrations: admin, auth, contenttypes, projects, sessions
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 auth.0012_alter_user_first_name_max_length… OK
Applying projects.0001_initial… OK
Applying sessions.0001_initial… OK

As you can see above, django applies migrations for the projects app and the inbuilt apps that come with django (admin, auth, contenttypes, sessions) and creates database tables for these apps. Every time you make a change to our models, we will need to run migrations.

Database Relationships

A single project can have multiple tasks. To represent model relationships, django uses the following database relationships

  • One-to-one relationship
  • One-to-many relationships
  • Many-to-many relationships

In our project, the relationship between Project and Task is one-to-many. In django, this relationship is represented using a ForeignKey field. A foreign key in the task model means that a project can have many tasks.

Open the models.py file and add a Task model

class Task(models.Model):
TODO="TO DO"
COMPLETED='COMPLETED'
INPROGRESS='IN-PROGRESS'
STATUS_CHOICES = [
(TODO, 'TO DO'),
(COMPLETED, 'COMPLETED'),
(INPROGRESS, 'IN-PROGRESS'),

]
title = models.CharField(max_length=150)
description = models.TextField(null=True,blank=True)
project = models.ForeignKey(Project,null=True,blank=True, on_delete=models.CASCADE)
assignee = models.ForeignKey(User, null=True,blank=True, on_delete=models.SET_NULL)
date_created = models.DateTimeField(auto_now_add=True)
due_date = models.DateField(null=True,blank=True)
status = models.CharField(max_length=50, choices=STATUS_CHOICES,null=True,blank=True,default= TODO)

Foreign keys must have an on_delete attribute. The on_delete attribute determines what happens when the associated relationships object is deleted. The field project is set to on_delete=models.CASCADE means that if a project is deleted, the tasks belonging to that project will also be deleted.

In the assignee field case, on_delete=models.SET_NULL means that when a task owner is deleted, all the tasks they had been assigned will not be deleted, and therefore they can be reassigned to another person. The assignee field is linked to a User model by a foreign key. We import the User model from django.contrib.auth.models.

We have also added another field status that takes multiple options as inputs.

Run migrations for the Task model

python manage.py makemigrations
python manage.py migrate

Dunder methods

Dunder methods in Django give more information about a django model. Dunder methods like __str__ give a human-readable representation of the model instance in the Django admin site.

Let’s add a __str__ method to our models. Update models.py to look like this:

class Project(models.Model):
name = models.CharField(max_length=200,null=True,blank=True)
description = models.TextField(null=True,blank=True)
date_created = models.DateTimeField(auto_now_add=True,null=True,blank=True)

def __str__(self):
return self.name

class Task(models.Model):
TODO="TO DO"
COMPLETED='COMPLETED'
INPROGRESS='IN-PROGRESS'
STATUS_CHOICES = [
(TODO, 'TO DO'),
(COMPLETED, 'COMPLETED'),
(INPROGRESS, 'IN-PROGRESS'),

]
title = models.CharField(max_length=150)
description = models.TextField(null=True,blank=True)
project = models.ForeignKey(Project,null=True,blank=True, on_delete=models.CASCADE)
assignee = models.ForeignKey(User, null=True,blank=True, on_delete=models.SET_NULL)
date_created = models.DateTimeField(auto_now_add=True)
due_date = models.DateField(null=True,blank=True)
status = models.CharField(max_length=50, choices=STATUS_CHOICES,null=True,blank=True,default= TODO)

def __str__(self):
return self.title

Meta Options

Suppose you wish to fetch project entries ordered by date_created; you can do that by specifying a meta class inside the django models, as shown below.

class Project(models.Model):
name = models.CharField(max_length=200,null=True,blank=True)
description = models.TextField(null=True,blank=True)
date_created = models.DateTimeField(auto_now_add=True,null=True,blank=True)

def __str__(self):
return self.name

class Meta:
ordering = ['-date_created']

By adding the Meta for ordering by -date_created, all the project entries will be ordered in descending order.

The Django ORM

The Django ORM provides a wrapper that allows you to make database queries. To get started making queries, we will use the Python interactive shell. Issue the following command in your terminal to start the Python interactive shell.

python manage.py shell

The python shell has django functionalities and allows us to interact with data in the database. In the shell prompt, import the models from the projects app.

>>> from projects.models import Project,Task
>>>

Creating objects

Let’s add the details of the first project with the name = IOS app development

>>> project1 = Project.objects.create(name = ‘IOS app development)
>>> print(project1)
IOS app development
>>>

In the code above, we create an instance of a Project called project1 and save it to the database. When you print the instance again, you get the string representation of the object (this is possible because we defined a __str__ method in the model)

You can also query attributes like the date_created and description.

>>> print(project1.date_created)
2023–05–09 12:16:59.156299+00:00
>>> print(project1.description)
None

As you can see, the description is None since we didn’t add any value to the description field.

Let’s add a task instance to project1; since project1 is the first entry, its id will be 1; we will first get the project instance using the get() method and then associate it with the new task.

>>> project1 =Project.objects.get(id=1)
>>> task1 = Task.objects.create(title=’Create interfaces’,project =project1, description = “Create interfaces for the IOS app”)

Updating objects

Update details of task1, first check the current status, update it, then save the new changes.

>>> task1.status
‘TO DO’
>>> task1.status = “COMPLETED”
>>> task1.save()
>>> task1.status
‘COMPLETED’
>>>

Deleting objects

To delete a task object issue the delete() method on the task instance as shown below

>>> task1.delete()
(1, {projects.Task’: 1})
>>>

Retrieving objects

You can also retrieve all the objects in the database using the all() method. Let’s retrieve all instances of the Project model

>>> Project.objects.all()
<QuerySet [<Project: Website development>, <Project: Android app development>, <Project: IOS App development>, <Project: IOS app developement>]>
>>>

You can also count the number of projects in the database, with the count method as shown below

>>> Project.objects.all().count()
4

Django Admin

We have covered how to use the django ORM to add and manipulate data; we can also add data via the django admin site.

Django has an admin site that provides an interface for managing your project data. The url for the django admin site is already set in the root urls.py file, as shown below.

from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
]

If you run the server and navigate to the admin url at http://127.0.0.1:8000/admin you should see a login screen.

To log in to the admin site, you must be a superuser. A superuser in django is a user with admin privileges. Issue the following command in your terminal to create a super user.

python manage.py createsuperuser

You will be prompted for some details such as username, email, and password, fill them out, and when you are done, you should get a confirmation that a superuser has been created successfully

Username: admin
Email address:
Password:
Password (again):
Superuser created successfully.

Rerun the server, navigate to http://127.0.0.1:8000/admin/, and login with the superuser credential; the site now looks like this:

As you can see above, the Django site allows us to add and change Groups and users. We can do the same with our Task and Project models.

When we created the project’s app, django created for us a file called admin.py; we will use the admin.py file to register the Project and Task models.

Open the admin.py file and add the following code.

# projects/admin/py
from django.contrib import admin
from .models import Task,Project

# Register your models here.
admin.site.register(Task)
admin.site.register(Project)

Refresh the admin site and see that the Task and Project models appear on the admin site.

Add a few project and task entries data via the django admin.

Summary.

We have come to the end of part 1. In this guide, you have learned how to:

  • Create and manage virtual environments
  • Create your first Django application
  • Create django apps
  • How to work with views to render templates
  • How to create templates, use variables, and conditional logic to render data in templates
  • How to use the Django ORM to create and query data in your database
  • How to create a superuser, and enable data in the django admin site.

Stay tuned for part 2, where we will cover CRUD functionality.

Update:

Read part 2 here.

If you want the full guide, Download the complete three-part series combined into a single PDF here.

Source Code

Level Up Coding

Thanks for being a part of our community! Before you go:

🚀👉 Join the Level Up talent collective and find an amazing job

--

--