Django Charts via DRF - Step-by-Step Tutorial
Tags: django, chartjs, dashboard, opensource
Published: 2022-05-01 07:04:00

---
title: Django Charts via DRF - Step-by-Step Tutorial
published: true
description: Sample project crafted with Django, Charts.JS, and DRF to showcase how to plot different charts Pie, Line, and Bar Charts.
tags: django, chartjs, dashboard, opensource
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yj0rv8kn5w3mufn6nm78.jpg
canonical_url: https://blog.appseed.us/django-charts-via-drf-and-charts-js/
---
Hello Coders! 

`Django` is a powerful web framework built with Python that you can use to create fully functional web applications or web APIs. This article explains `step-by-step` how to showcase three different charts (`pie`, `line`, and `bar` chart) using **[Django, Django REST Framework, and Chart.js](https://blog.appseed.us/django-charts-via-drf-and-charts-js/)**. The content presents all layers and concepts (API, JS, Html) and also provides the link to the `source code, saved on Github`. 

> Thanks for reading! Content provided by `App Generator`. 

- 👉 `Setup the Django` project
- 👉 Writing the `app models`
- 👉 Load `sample data`
- 👉 Code the `serializers`
- 👉 Writing the `API via DRF`
- 👉 Code the `Javascript` part
- 👉 Update `Configuration and routing`

---

- 👉 [Django Charts via DRF](https://github.com/app-generator/sample-django-charts-js) - source code
- 👉 More [Django Apps](https://appseed.us/apps/django/) and [Dashboards](https://appseed.us/admin-dashboards/django/)
- 👉 Free [support](https://appseed.us/support) via email and [Discord](https://discord.gg/fZC6hup).

---

![Django Charts via DRF and Chart.js - Cover Django.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3yvl7aq4dvp01vrm4a9c.jpg)

---

## ✨ Setup the project

Before going, make sure that, first of all, let’s set up the project. Feel free to use your favorite python environment management tool. I’ll be using `virtualenv` here.

```bash
$ virtualenv env
$ source env/bin/activate
```

After that, we install the libraries we’ll be using for the development and create the project.

```bash
$ pip install django 
$ pip install djangorestframework 
$ pip install djangorestframework-simplejwt 
$ pip install django-import-export
```

**Create the project**

```
$ django-admin startproject core
```

We’ll first create an app that will contain all the project-specific features.

```bash
$ django-admin startapp apps
```

After creating, delete all files and folders except `__init__.py`, `views.py`, `models.py` and `apps.py`. Then open the settings file containing Django configurations and add core to the `INSTALLED_APPS`:

```python
   # core/settings.py
    ...
    'django.contrib.messages',
    'django.contrib.staticfiles',
    
    'apps'
```

Great! Ensure that the project is well set up by running the following commands:

```bash
$ python manage.py makemigrations
$ python manage.py migrate
```

After that, run the following command to start the server.

```
$ python manage runserver
```

If you access `localhost:8000`, we should see the Django default splash screen:

![Django Charts via DRF - Default Django Screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jzixe1htojbhy9my6ff3.png)

---

## ✨ Writing the models

This project will contain two models:

- The User model.
- And the Product model.

Following the figure, we notice that the models have the same fields as created and updated. Before creating the application for each feature (user and product), let’s create a base model that will be used by the User model and the `Product` model.

**In the apps/models.py, add the following content.** 

```python
from django.db import models
from django.utils import timezone
 
class BaseModel(models.Model):
   created = models.DateTimeField(default=timezone.now)
   updated = models.DateTimeField(auto_now=True)
 
   class Meta:
       abstract = True
```

The `abstract=True` makes sure that no migrations are generated for this model. Let’s move to write the `User` model.

---

> 👉 **User model**

Before writing the model, we need to create a Django application. In the apps directory, enter the following command.

```bash
$ django-admin startapp user
```

Once it’s done, rewrite the `apps/user/apps.py` file.

```python
from django.apps import AppConfig
  
class UserConfig(AppConfig):
   default_auto_field = 'django.db.models.BigAutoField'
   name = 'apps.user'
   label = 'apps_user'
```

And we can register the application in the `settings.py` file now.

```python
INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
  
   'apps',                       # <-- OLD    
   'apps.user',                  # <-- NEW    
  
   'import_export',
   'rest_framework'
] 
```

**Let’s write the User model now based on the BaseModel class.**

```python
from django.contrib.auth.models import AbstractUser
from django.db import models
 
from apps.models import BaseModel  
 
class User(AbstractUser, BaseModel): 

   has_purchased = models.BooleanField(default=False)
```

Django provides a class called AbstractUser that comes with the following fields.

```python
username = models.CharField(
       _("username"),
       max_length=150,
       unique=True,
       help_text=_(
           "Required. 150 characters or fewer."
       ),
       validators=[username_validator],
       error_messages={
           "unique": _("A user with that username already exists."),
       },
   )
   first_name = models.CharField(_("first name"), max_length=150, blank=True)
   last_name = models.CharField(_("last name"), max_length=150, blank=True)
   email = models.EmailField(_("email address"), blank=True)
   is_staff = models.BooleanField(
       _("staff status"),
       default=False,
       help_text=_("Designates whether the user can log into this admin site."),
   )
   is_active = models.BooleanField(
       _("active"),
       default=True,
       help_text=_(
           "Designates whether this user should be treated as active. "
       ),
   )
   date_joined = models.DateTimeField(_("date joined"), default=timezone.now)
```

It’s very effective for what we want here. We are also adding a new field `has_purchased` to track a user who has made at least a purchase on the API. Before running the migrations and committing the changes to the database, let’s add in the settings.py a configuration for the `AUTH_USER_MODEL` that will be used by Django.

```python
...
AUTH_USER_MODEL = "apps_user.User"
...
```

**Now run the following commands:**

```bash
$ python manage.py makemigrations
$ python manage.py migrate
```

With the User model added, let’s write a custom admin class to allow data importation with files. `django-import-export` is a Django application and library for importing and exporting data with included admin integration.

**In the apps/user/admin.py, add the following content:**

```python
from django.contrib import admin
from import_export import resources
from import_export.admin import ImportMixin
 
from apps.user.models import User
 
class UserResource(resources.ModelResource):
   class Meta:
       model = User
       fields = ('id', 'username', 'email', 'has_purchased', 'created', 'updated')
      
@admin.register(User)
class UserAdmin(ImportMixin, admin.ModelAdmin):
   resource_class = UserResource
   list_display = ('id', 'username', 'email', 'has_purchased', 'created', 'updated')
   list_filter = ('has_purchased',)
   search_fields = ('username', 'email')
```

A resource defines how objects are mapped to their import and export representations and handle importing and exporting data. In this filed, we declare the UserResource class containing the model and the fields needed when data is imported.  

The UserAdmin class also takes the fields displayed, the field used for filtering data, and the fields used for search.

---

> 👉 **Product Model**

Before writing the `Product` model, let’s create a `Django` application called product.

```bash
$ django-admin startapp product 
```

After the application has been created, modify the `apps.py` file in the product directory.

```python
from django.apps import AppConfig  
 
class ProductConfig(AppConfig):
   default_auto_field = 'django.db.models.BigAutoField'
   name = 'apps.product'
   label = 'apps_product' 
```

**And then, let’s register the application in the settings.py file.**

```python
INSTALLED_APPS = [
   'django.contrib.admin',
   'django.contrib.auth',
   'django.contrib.contenttypes',
   'django.contrib.sessions',
   'django.contrib.messages',
   'django.contrib.staticfiles',
  
   'apps',
   'apps.product',               # <--- NEW 
   'apps.user',
  
   'import_export',
   'rest_framework'
] 
```

**We can now write the Product model.**

```python
from django.db import models
 
from apps.models import BaseModel
 
class Product(BaseModel):
   name = models.CharField(max_length=255)
   description = models.TextField()
   price = models.DecimalField(max_digits=10, decimal_places=2)
   quantity_sold = models.IntegerField(default=0)
 
   def __str__(self):
       return self.name
```

**We will also write a custom admin class for the Product model.** In the apps/product/admin.py, add the following content:

```python
from django.contrib import admin
from import_export import resources
from import_export.admin import ImportMixin
 
from apps.product.models import Product
 
class ProductResource(resources.ModelResource):
   class Meta:
       model = Product
       fields = ('id', 'name', 'price', 'description', 'created')
      
@admin.register(Product)
class ProductAdmin(ImportMixin, admin.ModelAdmin):
   resource_class = ProductResource
   list_display = ('id', 'name', 'price', 'description', 'created', 'updated')
   list_filter = ('created',)
   search_fields = ('name',)
```

Well, with both product admin and user admin written, we can now move to load the sample data in the database using the admin section (import/export feature).

---

## ✨ Load Sample Data  

Django provides an admin dashboard that you can use to manage resources (models, users, groups) from our Django project. But before accessing the dashboard, you need admin credentials.

In the shell, enter the following command to create a new superuser.

```bash
$ django-admin createsuperuser
```

Once the superuser is created, we can access the admin section of our project `localhost:admin/`. After you have successfully logged in, you’ll see a similar page.

![Django Charts via DRF - Admin Section](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8kye974uiufnl5ys8nze.png)

---

Let’s upload data for the users first. Clicking on Add, you’ll be redirected to the following page.

---

![Django Charts via DRF - Load User's Data.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xblkgqzjqrujd7ko741k.png)

You’ll normally see the first user that is the superuser you’ve just created. Great, now click on import. You’ll have to import a CSV file. You can find an example of this file here.

Once the file is imported, click on `Confirm Import`.

![Django Charts via DRF - Confirm Import Data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/da7io2n7ko62ydmkljux.png)
 
Your database is now filled with some users' data 🚀. The next step is to load the products data using the same import feature over the Products using the sample data.

---

## ✨ Writing serializers

Serializer allows us to convert Django data structures such as query sets or model instances in Python native objects that can be easily converted to JSON/XML format. Let’s start by writing a serializer for the User model.

> 👉 **User serializer**

Inside the apps/user, create a file called serializers.py. This file will contain a class called `UserSerializer`.

```python
from rest_framework import serializers

from apps.user.models import User
 
class UserSerializer(serializers.ModelSerializer):
  
   class Meta:
       model = User
       fields = ('id', 'username', 'email', 'has_purchased')
```
 
The `ModelSerializer` helps write serializers based on models very easier. It automatically matches the fields and their types and even adds some automatic validations.

---

> 👉 **Product serializer**

Inside the `apps/product/` directory, create a file called `serializers.py`. This file will contain a model serializer called ProductSerializer.

```python
from rest_framework import serializers
 
from apps.product.models import Product  
 
class ProductSerializer(serializers.ModelSerializer):
   class Meta:
       model = Product
       fields = ('id', 'name', 'price', 'description', 'created', 'updated')
```

Great! Now that we have serializers, we can write viewsets.

---

## ✨ Writing viewsets

A viewset is a class-based view, able to handle all of the basic HTTP requests: GET, POST, PUT, DELETE without hard coding any of the logic. We are using viewsets here to configure the routing for the API easily.

The `/user/` viewset should allow the following structure.

![Django Charts via DRF - Users Viewsets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cabhxwq03805bdpce7z7.png)

---

**And the product resource will have the following structure.**

![Django Charts via DRF - Product Viewsets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o7ukilh0dhj9mo6dbtmv.png)

---

> 👉 **Writing User viewset**

Inside the `apps/user/` directory, create a file called `viewsets.py`. The file will contain a class called UserViewSet.

```python
import calendar
 
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from rest_framework.decorators import action
 
from apps.user.models import User
from apps.user.serializers import UserSerializer
 
class UserViewSet(viewsets.ModelViewSet):
   http_method_names = ['get']
   queryset = User.objects.all()
   serializer_class = UserSerializer
  
   @action(detail=False, methods=['get'], url_path='stats/purchased')
   def purchase_stats(self, request, *args, **kwargs):
       users = User.objects.filter(has_purchased=True)
       return Response({
           'total_users': User.objects.count(),
           'total_purchased': users.count(),
           'purchased_percentage': round(users.count() / User.objects.count() * 100, 2)
       }, status=status.HTTP_200_OK)
      
  
   @action(detail=False, methods=['get'], url_path='stats/users_created')
   def user_created_per_month(self, request, *args, **kwargs):
       users = User.objects.all()
       months = []
       for user in users:
           months.append(user.created.date().month)
       months = list(set(months))
       months.sort()
       data = []
       for month in months:
           data.append({
               'month': calendar.month_name[month],
               'count': User.objects.filter(created__month=month).count()
           })
       return Response(data, status=status.HTTP_200_OK)
```

In this `viewset`, we are allowing only `GET` requests. And we are also adding custom actions to the viewset such as `purchase_stats` and `user_created_per_month`. These actions compute and return some useful stats.

Let’s do the same for the ProductViewSet.

---

> 👉 **Writing Products viewset**

Inside the `apps/product` directory, create a file called viewsets.py. This file will contain the `ProductViewSet` class.

```python
import calendar
 
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from rest_framework.decorators import action
 
from apps.product.models import Product
from apps.product.serializers import ProductSerializer
 
 
class ProductViewSet(viewsets.ModelViewSet):
   http_method_names = ['get']
   queryset = Product.objects.all()
   serializer_class = ProductSerializer
 
   @action(detail=False, methods=['get'], url_path='sales')
   def sales_over_month(self, request, *args, **kwargs):
       products = Product.objects.all()
       months = []
       for product in products:
           months.append(product.created.date().month)
       months = list(set(months))
       months.sort()
       data = []
       for month in months:
           data.append({
               'month': calendar.month_name[month],
               'count': Product.objects.filter(created__month=month).count()
           })
       return Response(data, status=status.HTTP_200_OK)
```

Great! With the view sets ready, we can register these view sets to create endpoints and start making requests.

---

## ✨ Writing API Endpoints

As we are working with `viewsets`, we can automatically register the API endpoints using routers.

**Inside the apps directory, create a file called routers.py.**

```python
from rest_framework import routers
 
from apps.user.viewsets import UserViewSet
from apps.product.viewsets import ProductViewSet
 
router = routers.SimpleRouter()  
 
router.register('user', UserViewSet)
router.register('product', ProductViewSet)
 
urlpatterns = [
   *router.urls,
]
```

We need then to register this file in the urls.py file of the project. And then these endpoints will be accessible even via the browser.

```python
from django.contrib import admin
from django.urls import path, include
 
urlpatterns = [
   path('admin/', admin.site.urls),
   path("api/", include(("apps.routers", "api"), namespace="api")),
] 
```

Great! After that, make sure the server is running because we will start making some requests.

---

## ✨ Integrating Chart.JS

`Chart.js is a JavaScript library that makes working and building charts easier`. In this project, we’ll use this library to display line, bar and pie charts.

The data displayed on the charts will come from the REST API we’ve just built and we’ll be using Axios to fetch data from the server.

But first, let’s make some configurations for the Django templating system and the static file such as the JavaScript file that will contain the logic for data fetching and charts configuration.

---

> 👉 **Configuration**

First of all, make sure you have the following configuration for TEMPLATES in the settings.py file.

```python
TEMPLATES = [
   {
       'BACKEND': 'django.template.backends.django.DjangoTemplates',
       'DIRS': [os.path.join(BASE_DIR, 'apps/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 templates will be created in the apps/template directory. Also, we need the following configuration for static files.

```python
STATIC_URL = 'static/'
 
STATICFILES_DIRS = [
   os.path.join(BASE_DIR, 'static'),
]
```

---

> 👉 **Index.html - Product HOMEpage**

In the `apps/templates`, create a file called `index.html`. We’ll just add basic imports for `bootstrap`, `axios` and `chartjs`.

```html
<html lang="en"> 
<head>
<meta charset="utf-8"> 

<!-- Chart JS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css">
          
<!-- Chart JS -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
 
<!-- Axios -->
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
 
<title>Stats</title>
</head>
<body> 

<h1 class="text-center">Product Stats</h1>

<div class="row">
	
    <!-- LINE Chart -->
    <div class="col-6 mr-2">
        <h2 class="text-center">
            User created over months
        </h2>
        <canvas id="user-created"></canvas>
     </div>
    
    <!-- Bar Chart -->
    <div class="col-6 ml-2">
        <h2 class="text-center">
            Product sales over months
        </h2>
        <canvas id="product-sales"></canvas>
    </div>
        
    <!-- PIE Chart -->    
    <div class="col-4 mx-auto">
        <h2 class="text-center">
            Percentage of users who bought a product
        </h2>
        <canvas id="user-purchased"></canvas>
     </div> 
    
</div>
```

Once we have a minimum HTML file, we need to code the JS code that consumes the API and injects the data into the charts:

---

> 👉 **Index.JS - Charts and API**

At the root of the project, create a directory called static. Inside this directory create another directory called js.  This directory will contain the static JavaScript files. And finally, create a index.js file in the newly-created directory.

Let’s write the steps needed for fetching and configuring charts.

- Adding a function to fetch data and return a response
- Retrieve data from each endpoint
- Initialize the data with labels
- Initialize the chart configuration
- And finally, create a chart in the DOM.

Let's break down each step and visualize the necessary code to properly render our beautiful charts.

**1# - Adding a function to fetch data and return a response**

```javascript
async function getData(url) {
   const res = await axios.get(url);
 
   return await res.data;
```

---

**2# - Retrieve data from each endpoint**

```javascript
let userPurchased = {};
let productSales = [];
let userCreated = [];
 
// Fetching data for users that have purchased products
 
userPurchased = getData('/api/user/stats/purchased');
 
// Fetching data for users created over month
 
userCreated = getData('/api/user/stats/users_created');
 
// Fetching data for products sold over month
 
productSales = getData('/api/product/sales');
```

---

**3# - Inject the data into Charts**

Let’s initialize the data with labels, initialize the chart configuration and finally create a chart in the DOM for the `userCreate` for example.

```javascript
userCreated.then((response) => {
   const dataUserCreated = {
       labels: response.map((data) => data.month),
       datasets: [{
           label: 'Users that have purchased products',
           backgroundColor: 'rgb(255, 99, 132)',
           borderColor: 'rgb(255, 99, 132)',
           data: response.map((data) => data.count),
       }]
   };
 
   const configUserCreated = {
       type: 'line',
       data: dataUserCreated,
       options: {}
   };
 
   // Creating new chart
 
   new Chart(
       document.getElementById('user-created'),
       configUserCreated
   );
}) 
```

And let’s do the same for `userPurchased` and the `productSales`.

```javascript
productSales.then((response) => {
   const dataProductSales = {
       labels: response.map((data) => data.month),
       datasets: [{
           label: 'Products sold',
           data: response.map((data) => data.count),
           borderWidth: 1,
           backgroundColor: 'rgb(255, 99, 132)',
       }]
   };
 
   const configProductSales = {
       type: 'bar',
       data: dataProductSales,
       options: {
           scales: {
               y: {
                   beginAtZero: true
               }
           }
       },
   };
 
   new Chart(
       document.getElementById('product-sales'),
       configProductSales
   );
});
```

---

```javascript
userPurchased.then((response) => {
   const dataUserPurchased = {
       labels: [
           'Total Users',
           'Total Purchased',
           'Purchased percentage'
       ],
       datasets: [{
           label: 'Users created',
           data: Object.values(response),
           backgroundColor: [
               'rgb(255, 99, 132)',
               'rgb(54, 162, 235)',
               'rgb(255, 205, 86)'
           ],
           hoverOffset: 4
       }]
   };
 
 
   const configUserPurchased = {
       type: 'pie',
       data: dataUserPurchased,
   };
 
 
   new Chart(
       document.getElementById('user-purchased'),
       configUserPurchased
   );
});
```

Once the JS part is fully coded, the last step is to update the product routing and start using the project.

**In the `apps directory`, create a `views.py` file, and add the following content:**

```python
from django.template import loader
from django.shortcuts import render
def index(request):
   context = {"segment": "index"}
 
   return render(request, "index.html", context) 
```

And add the `urls.py` to the apps directory with the following content.

```python
from django.urls import path

from . import views
 
urlpatterns = [
   # The home page
   path("", views.index, name="home"),
] 
```

And register the `apps/urls.py` in the project `urls.py` file.

```python
..
urlpatterns = [
   path('admin/', admin.site.urls),
   path('', include('apps.urls')),
   path("api/", include(("apps.routers", "api"), namespace="api")),
]
.. 
```

**Make sure the server is running then and visit the app in the browser**. At this point, we should see the three charts beautifully rendered on the home page.  

---

![Django Charts via DRF and Charts.JS: Bar, Line, and Pie Charts.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tepogxibhal6q32crtnt.png)

----
 
## ✨ Conclusion

In this article, we’ve learned to build an API that serves statistics using `Django` and `Django REST Framework` (DRF). We’ve also created an HTML template to display data served by the REST API using `Chart.js` and `Axios`.

> `Thanks for reading!` For more resources, feel free to access:

- ✨ More [Free Dashboards](https://appseed.us/admin-dashboards/open-source) crafted in Django, Flask, and [React](https://appseed.us/apps/react)
- ✨ More [Admin Dashboards](https://www.admin-dashboards.com/) - a huge index with products