How to use charts to visualize Django Models

Hello everyone and welcome to this tutorial where we will learn something cool. My name is Nick.
So in this post, I'm going to show you how you can visualize data from your Django applications by displaying it in charts using chart js library. Without much talk let's get started, shall we?

PROJECT SET UP

Let's start by creating a new Django project.It is going to be a project that contains books and purchases related to the books. Navigate to your desktop directory and spin up a terminal window then issue the following commands.

mkdir django_charts_books_project && cd django_charts_books_project

VIRTUAL ENVIRONMENTS

To keep our project isolated from the rest in our machine, we need to use a virtual environment in which we can install only the packages we need. This way, any system wide changes to Python versions won't affect our little beautiful project.
Inside the folder you just created:

virtualenv env

Feel free to use pipenv or any other pyenv command line tools.
To activate that environment do this:

source env/bin/activate

Next, let's install Django and start a new Django project.

pip install django #1

django-admin startproject books_project . #2

python manage.py startapp books #3

Next up, open books_project/settings.py file and ensure you add 'books', to the list of INSTALLED_APPS section.

MODELS

Let us setup our model classes that will store book and purchase information.

from django.db import models

# Create your models here.
class Book(models.Model):
    title=models.CharField(max_length=250)
    author=models.CharField(max_length=250)
    price=models.FloatField(default=0)

    def __str__(self) -> str:
        return str(self.title)


class Purchase(models.Model):
    PAYMENT_METHODS=[
    ('Mpesa','Mpesa'),
    ('Card','Card'),
    ('Cash','Cash')
    ]
    customer=models.CharField(max_length=250)
    book=models.ForeignKey('Book',on_delete=models.CASCADE)
    payment_method=models.CharField(max_length=6, choices=PAYMENT_METHODS)
    time_created=models.DateTimeField(auto_now_add=True)
    is_successful=models.BooleanField(default=True)

    def __str__(self) -> str:
        return str(self.book)

So, a book instance will capture the book title, its author and the price it goes for.
A purchase instance will store the name of the customer, the book in question, the method the customer used to pay, the time that purchase was made and whether the purchase went through or not.
Remember to register your models in admin.py
After defining how our models look like, let us prepare the database:

python manage.py makemigrations #sync the db

python manage.py migrate #apply migrations

For this project we are going to use the default db.sqlite3 database that ships with Django.

INITIAL DATA

We need to populate our database, one way is to create a superuser and use the admin section of the project to add data. Another way is to employ creativity and automate this. I prefer the latter so create a new folder inside 'books' called 'management', and then inside that folder create a new folder called 'commands' in which create file called populate_db.py
This file would look like so:

import random
from datetime import date, datetime, time,timedelta
import pytz
from django.core.management.base import BaseCommand
from books.models import Book, Purchase
from random import randint


#will use to generate a 3 digit number to act as price of a book
def random_with_N_digits(n):
    range_start = 10**(n-1)
    range_end = (10**n)-1
    return randint(range_start, range_end)

class Command(BaseCommand):
    help='Populates the db wit random generated data'

    def add_arguments(self, parser):
        parser.add_argument('--amount',type=int, help='The number of purchases that should be created.')

    def handle(self, *args, **kwargs):
        books= [
                'Sapiens',
                'Man’s search for meaning',
                'The War of Art',
                'How to win friends and influence people',
                'Meditation',
                'Shoe Dog',
                'The Predatory Female',
                'The Rationale Male - Religion',
                'The lean startup',
                'In Pursuit of Purpose',
                'The 4 -Hour work week',
                '7 habits of Highly Effective Men',
                'As a Man Thinketh',
                'The Intelligent Investor',
                'How Not To Die',
                'The Manipulated Man',
                'The Lessons of History',
                'Why we eat too much',
                'The Art of War',
                'Can’t hurt me',
                'Made to Stick',
                'Atomic Habits',
                'Homo Deus',
                '12 Rules for Life',
                'The Power of Habit',
                'Good to Great',
                'Why we sleep',
                'The Emperor of All Maladies',
                'Why Nations Fail',
                'Blood and Oil'
                ]
        authors=[
                'Yuval Harari',
                'Viktor Frankl',
                'Steven Pressfield',
                'Dale Carnegie',
                'Marcus Aurelius',
                'Phil Knight',
                'Lawrence Shannon',
                'Rollo Tomassi',
                'Eric Ries',
                'Myles Munroe',
                'Tim Ferris',
                'Stephen R Covey',
                'James Allen',
                'Benjamin Graham',
                'Dr. Michael Greger',
                'Esther Vilar',
                'Will & Ariel Durant',
                'Dr. Andrew Jenkinson',
                'Sun Tzu',
                'David Goggins',
                'Chip Heath & Dan Heath',
                'James Clear',
                'Noah Harari',
                'Jordan B Peterson',
                'Charles Duhigg',
                'Jim Collins',
                'Matthew Walker',
                'Siddhartha Mukherjee',
                'Daron Acemoglu & James Robinson',
                'Bradley Hope & Justin Scheck',
                ]
        prices=[]
        customers=['Nick','Kibet','Vin','Lyn','Mary','John','Dan','Doro']
        for i in range(31):
            price=random_with_N_digits(3)
            prices.append(price)
        book_list=[]
        for book, author, price in zip(books, authors, prices):
            x=Book.objects.get_or_create(
                title=book,
                author=author,
                price=price
            )
            book_list.append(x)
            print(book_list)
        amount=kwargs['amount'] if kwargs['amount'] else 150
        for i in range(0, amount):
            dt=pytz.utc.localize(datetime.now() - timedelta(days=random.randint(0, 1200)))
            purchase = Purchase.objects.create(
                customer=random.choice(customers),
                book=random.choice(book_list)[0],
                payment_method=random.choice(Purchase.PAYMENT_METHODS)[0],
                is_successful=True if random.randint(1,2)==1 else False

            )
            purchase.time_created=dt
            print(purchase)
            purchase.save()

        self.stdout.write(
            self.style.SUCCESS(
                'Done populating the database'
            )
        )

Good, with that file we can populate our database with some data and begin playing around with. In your terminal while the env is activated:

python manage.py populate_db

To check if the data was actually saved, create a superuser using python manage.py createsuperuser then head over to (http://localhost:8000/admin/) and check.

CHART LOGIC

Now that we have some data, we can start manipulating it.
Within 'books' folder create a file called utils.py. Inside it paste the follwing code:

months = [
    'January', 'February', 'March', 'April',
    'May', 'June', 'July', 'August',
    'September', 'October', 'November', 'December'
]
colorPalette = ['#00ccff ', '#ff33cc', '#ff0066', '#00ffcc', '#290066', '#ff3300', '#ffff00']
colorPrimary, colorSuccess, colorDanger = '#79aec8', colorPalette[0], colorPalette[5]


def get_year_dict():
    year_dict = dict()

    for month in months:
        year_dict[month] = 0

    return year_dict


def generate_color_palette(amount):
    palette = []

    i = 0
    while i < len(colorPalette) and len(palette) < amount:
        palette.append(colorPalette[i])
        i += 1
        if i == len(colorPalette) and len(palette) < amount:
            i = 0

    return palette

Moving on let's write the views to render our model data. Open views.py and add the code below:

from django.shortcuts import render
from django.db.models import Count,F,Sum,Avg
from django.db.models.functions import ExtractYear,ExtractMonth
from django.http import JsonResponse
from .models import Book, Purchase
from .utils import (
    months,colorDanger,
    colorPrimary,colorSuccess,
    generate_color_palette,get_year_dict)
# Create your views here.

def display_charts(request):
    return render(request, 'charts.html', {})

def filter_options(request):
    merged_purchases=Purchase.objects.annotate(
        year=ExtractYear(
            'time_created'
        )).values(
            'year'
            ).order_by(
                '-year'
                ).distinct()
    options= [purchase['year'] for purchase in merged_purchases]

    return JsonResponse(data={
        'options':options
    })



def get_annual_sales(request, year):
    purchases=Purchase.objects.filter(time_created__year=year)
    merged_purchases=purchases.annotate(
        price=F('book__price')
    ).annotate(month=ExtractMonth('time_created')).values(
        'month'
    ).annotate(
        average=Sum(
            'book__price'
        )
    ).values(
        'month',
        'average'
    ).order_by('month')
    sales_dict=get_year_dict()
    for merge in merged_purchases:
        sales_dict[months[merge['month']-1]]=round(merge['average'], 2)

    return JsonResponse({
        'title':f'Sales in {year}',
        'data':{
            'labels':list(sales_dict.keys()),
            'datasets':[{
                'label':'Amount (KSHS)',
                'backgroundColor':generate_color_palette(7),
                'borderColor':generate_color_palette(5),
                'data':list(sales_dict.values())
            }]
        }
    })

Then add a route to it inside 'books/urls.py' like so:

from django.urls import path, include
from .import views
urlpatterns = [
    path('', views.display_charts, name='index'),
    path('filters', views.filter_options,name='filter_options'),
    path('annual/<int:year>/sales', views.get_annual_sales, name='annual_chart'),
]

TEMPLATE

We now move to the user interface since we have the logic ready. Update your settings.py with the following code inside the DIRS array of the TEMPLATES SECTION:

'DIRS': [os.path.join(BASE_DIR, 'templates')],

With that in place create a project level folder called 'templates' and in it create a file called charts.html.
Fill that file with the code found on this paste.

SPIN UP THE SERVER

Up to this point, congrats! Just a few more steps. On the terminal issue:

python manage.py runserver

Head over to (http://localhost:8000/) to view your charts!

DONE AND DUSTED

If you made it this far then a well done well deserved! We were able to use data from our models to render beautiful charts using chart js library. The code to this project can be accessed from my repo.
If you enjoyed this post then be sure to follow me as I will be dropping 2 articles each week. Oh hey am also on Twitter.
Cheers!

16