How to use charts to visualize Django Models
Tags: django, javascript, python
Published: 2021-07-24 09:33:11

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](https://www.chartjs.org/). 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.
```python
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.
```python
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
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:
```python
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:
```python
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:
```python
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:
```python
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`:
```python
 '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](https://dpaste.org/wSgq).
##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!

![Model charts](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qae5vxwy3c46i909qte2.png)
##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](https://github.com/nicksonlangat/django-with-charts).
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](https://twitter.com/NicksonLangat20).
Cheers!