In this tutorial, we'll look at how to add interactive charts to the Django with Chart.js. We'll use Django to model and prepare the data and then fetch it asynchronously from our template using AJAX. Finally, we'll look at how to create new Django admin views and extend existing admin templates in order to add custom charts to the Django admin.
Contents
What is Chart.js?
Chart.js is an open-source JavaScript library for data visualization. It supports eight different chart types: bar, line, area, pie, bubble, radar, polar, and scatter. It's flexible and highly customizable. It supports animations. Add best of all, it's easy to use.
To get started, you just need to include the Chart.js script along with a <canvas>
node in an HTML file:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
<canvas id="chart" width="500" height="500"></canvas>
Then, you can create a chart like so:
let ctx = document.getElementById("chart").getContext("2d");
let chart = new Chart(ctx, {
type: "bar",
data: {
labels: ["2020/Q1", "2020/Q2", "2020/Q3", "2020/Q4"],
datasets: [
{
label: "Gross volume ($)",
backgroundColor: "#79AEC8",
borderColor: "#417690",
data: [26900, 28700, 27300, 29200]
}
]
},
options: {
title: {
text: "Gross Volume in 2020",
display: true
}
}
});
This code creates the following chart:
See the Pen wvogmJy by Michael Herman (@mjhea0) on CodePen.
To learn more about Chart.js check out the official documentation.
With that, let's look at how to add charts to Django with Chart.js.
Project Setup
Let's create a simple shop application. We'll generate sample data using a Django management command and then visualize it with Chart.js.
Prefer a different JavaScript chart library like D3.js or Chartist? You can use the same approach for adding the charts to Django and the Django admin. You'll just need to adjust how data is formatted in your JSON endpoints.
Workflow:
- Fetch data using Django ORM queries
- Format the data and return it via a protected endpoint
- Request the data from a template using AJAX
- Initialize Chart.js and load the data
Start by creating a new directory and setting up a new Django project:
$ mkdir django-interactive-charts && cd django-interactive-charts
$ python3.11 -m venv env
$ source env/bin/activate
(env)$ pip install django==4.2
(env)$ django-admin startproject core .
After that, create a new app called shop
:
(env)$ python manage.py startapp shop
Register the app in core/settings.py under INSTALLED_APPS
:
# core/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"shop.apps.ShopConfig", # new
]
Create Database Models
Next, create Item
and Purchase
models:
# shop/models.py
from django.db import models
class Item(models.Model):
name = models.CharField(max_length=255)
description = models.TextField(null=True)
price = models.FloatField(default=0)
def __str__(self):
return f"{self.name} (${self.price})"
class Purchase(models.Model):
customer_full_name = models.CharField(max_length=64)
item = models.ForeignKey(to=Item, on_delete=models.CASCADE)
PAYMENT_METHODS = [
("CC", "Credit card"),
("DC", "Debit card"),
("ET", "Ethereum"),
("BC", "Bitcoin"),
]
payment_method = models.CharField(max_length=2, default="CC", choices=PAYMENT_METHODS)
time = models.DateTimeField(auto_now_add=True)
successful = models.BooleanField(default=False)
class Meta:
ordering = ["-time"]
def __str__(self):
return f"{self.customer_full_name}, {self.payment_method} ({self.item.name})"
Item
represents an item in our storePurchase
represents a purchase (that's linked to anItem
)
Make migrations and then apply them:
(env)$ python manage.py makemigrations
(env)$ python manage.py migrate
Register the models in shop/admin.py:
# shop/admin.py
from django.contrib import admin
from shop.models import Item, Purchase
admin.site.register(Item)
admin.site.register(Purchase)
Populate the Database
Before creating any charts, we need some data to work with. I've created a simple command we can use to populate the database.
Create a new folder in "shop" called "management", and then inside that folder create another folder called "commands". Within the "commands" folder, create a new file called populate_db.py.
management
└── commands
└── populate_db.py
populate_db.py:
# shop/management/commands/populate_db.py
import random
from datetime import datetime, timedelta
import pytz
from django.core.management.base import BaseCommand
from shop.models import Item, Purchase
class Command(BaseCommand):
help = "Populates the database with 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, **options):
names = ["James", "John", "Robert", "Michael", "William", "David", "Richard", "Joseph", "Thomas", "Charles"]
surname = ["Smith", "Jones", "Taylor", "Brown", "Williams", "Wilson", "Johnson", "Davies", "Patel", "Wright"]
items = [
Item.objects.get_or_create(name="Socks", price=6.5), Item.objects.get_or_create(name="Pants", price=12),
Item.objects.get_or_create(name="T-Shirt", price=8), Item.objects.get_or_create(name="Boots", price=9),
Item.objects.get_or_create(name="Sweater", price=3), Item.objects.get_or_create(name="Underwear", price=9),
Item.objects.get_or_create(name="Leggings", price=7), Item.objects.get_or_create(name="Cap", price=5),
]
amount = options["amount"] if options["amount"] else 2500
for i in range(0, amount):
dt = pytz.utc.localize(datetime.now() - timedelta(days=random.randint(0, 1825)))
purchase = Purchase.objects.create(
customer_full_name=random.choice(names) + " " + random.choice(surname),
item=random.choice(items)[0],
payment_method=random.choice(Purchase.PAYMENT_METHODS)[0],
successful=True if random.randint(1, 2) == 1 else False,
)
purchase.time = dt
purchase.save()
self.stdout.write(self.style.SUCCESS("Successfully populated the database."))
Run the following command to populate the DB:
(env)$ python manage.py populate_db --amount 1000
If everything went well you should see a Successfully populated the database.
message in the console.
This added 8 items and 1,000 purchases to the database.
Prepare and Serve the Data
Our app will have the following endpoints:
chart/filter-options/
lists all the years we have records forchart/sales/<YEAR>/
fetches monthly gross volume data by yearchart/spend-per-customer/<YEAR>/
fetches monthly spend per customer by yearchart/payment-success/<YEAR>/
fetches yearly payment success datachart/payment-method/<YEAR>/
fetches yearly payment method data (Credit card, Debit card, Ethereum, Bitcoin)
Before we add the views, let's create a few utility functions that will make it easier to create the charts. Add a new folder in the project root called "utils". Then, add a new file to that folder called charts.py:
# util/charts.py
months = [
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"
]
colorPalette = ["#55efc4", "#81ecec", "#a29bfe", "#ffeaa7", "#fab1a0", "#ff7675", "#fd79a8"]
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
So, we defined our chart colors and created the following two methods:
get_year_dict()
creates a dictionary of months and values, which we'll use to add the monthly data to.generate_color_palette(amount)
generates a repeating color palette that we'll pass to our charts.
Views
Create the views:
# shop/views.py
from django.contrib.admin.views.decorators import staff_member_required
from django.db.models import Count, F, Sum, Avg
from django.db.models.functions import ExtractYear, ExtractMonth
from django.http import JsonResponse
from shop.models import Purchase
from utils.charts import months, colorPrimary, colorSuccess, colorDanger, generate_color_palette, get_year_dict
@staff_member_required
def get_filter_options(request):
grouped_purchases = Purchase.objects.annotate(year=ExtractYear("time")).values("year").order_by("-year").distinct()
options = [purchase["year"] for purchase in grouped_purchases]
return JsonResponse({
"options": options,
})
@staff_member_required
def get_sales_chart(request, year):
purchases = Purchase.objects.filter(time__year=year)
grouped_purchases = purchases.annotate(price=F("item__price")).annotate(month=ExtractMonth("time"))\
.values("month").annotate(average=Sum("item__price")).values("month", "average").order_by("month")
sales_dict = get_year_dict()
for group in grouped_purchases:
sales_dict[months[group["month"]-1]] = round(group["average"], 2)
return JsonResponse({
"title": f"Sales in {year}",
"data": {
"labels": list(sales_dict.keys()),
"datasets": [{
"label": "Amount ($)",
"backgroundColor": colorPrimary,
"borderColor": colorPrimary,
"data": list(sales_dict.values()),
}]
},
})
@staff_member_required
def spend_per_customer_chart(request, year):
purchases = Purchase.objects.filter(time__year=year)
grouped_purchases = purchases.annotate(price=F("item__price")).annotate(month=ExtractMonth("time"))\
.values("month").annotate(average=Avg("item__price")).values("month", "average").order_by("month")
spend_per_customer_dict = get_year_dict()
for group in grouped_purchases:
spend_per_customer_dict[months[group["month"]-1]] = round(group["average"], 2)
return JsonResponse({
"title": f"Spend per customer in {year}",
"data": {
"labels": list(spend_per_customer_dict.keys()),
"datasets": [{
"label": "Amount ($)",
"backgroundColor": colorPrimary,
"borderColor": colorPrimary,
"data": list(spend_per_customer_dict.values()),
}]
},
})
@staff_member_required
def payment_success_chart(request, year):
purchases = Purchase.objects.filter(time__year=year)
return JsonResponse({
"title": f"Payment success rate in {year}",
"data": {
"labels": ["Successful", "Unsuccessful"],
"datasets": [{
"label": "Amount ($)",
"backgroundColor": [colorSuccess, colorDanger],
"borderColor": [colorSuccess, colorDanger],
"data": [
purchases.filter(successful=True).count(),
purchases.filter(successful=False).count(),
],
}]
},
})
@staff_member_required
def payment_method_chart(request, year):
purchases = Purchase.objects.filter(time__year=year)
grouped_purchases = purchases.values("payment_method").annotate(count=Count("id"))\
.values("payment_method", "count").order_by("payment_method")
payment_method_dict = dict()
for payment_method in Purchase.PAYMENT_METHODS:
payment_method_dict[payment_method[1]] = 0
for group in grouped_purchases:
payment_method_dict[dict(Purchase.PAYMENT_METHODS)[group["payment_method"]]] = group["count"]
return JsonResponse({
"title": f"Payment method rate in {year}",
"data": {
"labels": list(payment_method_dict.keys()),
"datasets": [{
"label": "Amount ($)",
"backgroundColor": generate_color_palette(len(payment_method_dict)),
"borderColor": generate_color_palette(len(payment_method_dict)),
"data": list(payment_method_dict.values()),
}]
},
})
Notes:
get_filter_options()
fetches all the purchases, groups them by year, extracts the year from the time field, and returns them in a list.get_sales_chart()
fetches all the purchases (in a specific year) and their prices, groups them by month, and calculates the monthly sum of prices.spend_per_customer_chart()
fetches all the purchases (in a specific year) and their prices, groups them by month, and calculates the average monthly price.payment_success_chart()
counts successful and unsuccessful purchases in a specific year.payment_method_chart()
fetches all the purchases (in a specific year), groups them by payment method, counts the purchases, and returns them in a dictionary.
Note that every view has the @staff_member_required decorator.
URLs
Create the following app-level URLs:
# shop/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("chart/filter-options/", views.get_filter_options, name="chart-filter-options"),
path("chart/sales/<int:year>/", views.get_sales_chart, name="chart-sales"),
path("chart/spend-per-customer/<int:year>/", views.spend_per_customer_chart, name="chart-spend-per-customer"),
path("chart/payment-success/<int:year>/", views.payment_success_chart, name="chart-payment-success"),
path("chart/payment-method/<int:year>/", views.payment_method_chart, name="chart-payment-method"),
]
Then, wire up the app URLs to the project URLs:
# core/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("shop/", include("shop.urls")), # new
]
Testing
Now that we registered the URLs, let's test the endpoints to see if everything works correctly.
Create a superuser, and then run the development server:
(env)$ python manage.py createsuperuser
(env)$ python manage.py runserver
Then, in your browser of choice, navigate to http://localhost:8000/shop/chart/filter-options/. You should see something like:
{
"options": [
2023,
2022,
2021,
2020,
2019,
2018
]
}
To view the monthly sales data for 2022, navigate to http://localhost:8000/shop/chart/sales/2022/:
{
"title": "Sales in 2022",
"data": {
"labels": [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
],
"datasets": [
{
"label": "Amount ($)",
"backgroundColor": "#79aec8",
"borderColor": "#79aec8",
"data": [
134.0,
104.0,
97.0,
100.0,
144.5,
118.0,
116.5,
153.0,
130.0,
143.0,
149.0,
114.0
]
}
]
}
}
Create Charts with Chart.js
Moving on, let's wire up Chart.js.
Add a "templates" folder to "shop". Then, add a new file called statistics.html:
<!-- shop/templates/statistics.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<title>Statistics</title>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
<script
src="https://code.jquery.com/jquery-3.6.4.min.js"
integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8="
crossorigin="anonymous">
</script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/bootstrap-grid.min.css">
</head>
<body>
<div class="container">
<form id="filterForm">
<label for="year">Choose a year:</label>
<select name="year" id="year"></select>
<input type="submit" value="Load" name="_load">
</form>
<div class="row">
<div class="col-6">
<canvas id="salesChart"></canvas>
</div>
<div class="col-6">
<canvas id="paymentSuccessChart"></canvas>
</div>
<div class="col-6">
<canvas id="spendPerCustomerChart"></canvas>
</div>
<div class="col-6">
<canvas id="paymentMethodChart"></canvas>
</div>
</div>
<script>
let salesCtx = document.getElementById("salesChart").getContext("2d");
let salesChart = new Chart(salesCtx, {
type: "bar",
options: {
responsive: true,
title: {
display: false,
text: ""
}
}
});
let spendPerCustomerCtx = document.getElementById("spendPerCustomerChart").getContext("2d");
let spendPerCustomerChart = new Chart(spendPerCustomerCtx, {
type: "line",
options: {
responsive: true,
title: {
display: false,
text: ""
}
}
});
let paymentSuccessCtx = document.getElementById("paymentSuccessChart").getContext("2d");
let paymentSuccessChart = new Chart(paymentSuccessCtx, {
type: "pie",
options: {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 1,
title: {
display: false,
text: ""
},
layout: {
padding: {
left: 0,
right: 0,
top: 0,
bottom: 25
}
}
}
});
let paymentMethodCtx = document.getElementById("paymentMethodChart").getContext("2d");
let paymentMethodChart = new Chart(paymentMethodCtx, {
type: "pie",
options: {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 1,
title: {
display: false,
text: ""
},
layout: {
padding: {
left: 0,
right: 0,
top: 0,
bottom: 25
}
}
}
});
</script>
</div>
</body>
</html>
This block of code creates the HTML canvases that Chart.js uses to initialize. We also passed responsive
to each charts' options so it adjusts based on the window size.
Add the following script to your HTML file:
<script>
$(document).ready(function() {
$.ajax({
url: "/shop/chart/filter-options/",
type: "GET",
dataType: "json",
success: (jsonResponse) => {
// Load all the options
jsonResponse.options.forEach(option => {
$("#year").append(new Option(option, option));
});
// Load data for the first option
loadAllCharts($("#year").children().first().val());
},
error: () => console.log("Failed to fetch chart filter options!")
});
});
$("#filterForm").on("submit", (event) => {
event.preventDefault();
const year = $("#year").val();
loadAllCharts(year)
});
function loadChart(chart, endpoint) {
$.ajax({
url: endpoint,
type: "GET",
dataType: "json",
success: (jsonResponse) => {
// Extract data from the response
const title = jsonResponse.title;
const labels = jsonResponse.data.labels;
const datasets = jsonResponse.data.datasets;
// Reset the current chart
chart.data.datasets = [];
chart.data.labels = [];
// Load new data into the chart
chart.options.title.text = title;
chart.options.title.display = true;
chart.data.labels = labels;
datasets.forEach(dataset => {
chart.data.datasets.push(dataset);
});
chart.update();
},
error: () => console.log("Failed to fetch chart data from " + endpoint + "!")
});
}
function loadAllCharts(year) {
loadChart(salesChart, `/shop/chart/sales/${year}/`);
loadChart(spendPerCustomerChart, `/shop/chart/spend-per-customer/${year}/`);
loadChart(paymentSuccessChart, `/shop/chart/payment-success/${year}/`);
loadChart(paymentMethodChart, `/shop/chart/payment-method/${year}/`);
}
</script>
When the page loads this script sends an AJAX request to /chart/filter-options/
to fetch all the active years and loads them into the form.
loadChart
loads chart data from the Django endpoint into the chartloadAllCharts
loads all the charts
Note that we used jQuery to handle the AJAX requests for simplicity. Feel free to use the Fetch API instead.
Inside shop/views.py create a new view:
@staff_member_required
def statistics_view(request):
return render(request, "statistics.html", {})
Don't forget the import:
from django.shortcuts import render
Assign a URL to the view:
# shop/urls.py
from django.urls import path
from . import views
urlpatterns = [
path("statistics/", views.statistics_view, name="shop-statistics"), # new
path("chart/filter-options/", views.get_filter_options, name="chart-filter-options"),
path("chart/sales/<int:year>/", views.get_sales_chart, name="chart-sales"),
path("chart/spend-per-customer/<int:year>/", views.spend_per_customer_chart, name="chart-spend-per-customer"),
path("chart/payment-success/<int:year>/", views.payment_success_chart, name="chart-payment-success"),
path("chart/payment-method/<int:year>/", views.payment_method_chart, name="chart-payment-method"),
]
Your charts are now accessible at: http://localhost:8000/shop/statistics/.
Add Charts to the Django Admin
With regard to integrating charts into the Django admin, we can:
- Create a new Django admin view
- Extend an existing admin template
Create a New Django Admin View
Creating a new Django admin view is the cleanest and the most straightforward approach. In this approach, we'll create a new AdminSite and change it in the settings.py file.
First, within "shop/templates", add an "admin" folder. Add a statistics.html template to it:
<!-- shop/templates/admin/statistics.html -->
{% extends "admin/base_site.html" %}
{% block content %}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
<script
src="https://code.jquery.com/jquery-3.6.4.min.js"
integrity="sha256-oP6HI9z1XaZNBrJURtCoUT5SUnxFr8s3BzRl+cbzUq8="
crossorigin="anonymous">
</script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/bootstrap-grid.min.css">
<form id="filterForm">
<label for="year">Choose a year:</label>
<select name="year" id="year"></select>
<input type="submit" value="Load" name="_load">
</form>
<script>
$(document).ready(function() {
$.ajax({
url: "/shop/chart/filter-options/",
type: "GET",
dataType: "json",
success: (jsonResponse) => {
// Load all the options
jsonResponse.options.forEach(option => {
$("#year").append(new Option(option, option));
});
// Load data for the first option
loadAllCharts($("#year").children().first().val());
},
error: () => console.log("Failed to fetch chart filter options!")
});
});
$("#filterForm").on("submit", (event) => {
event.preventDefault();
const year = $("#year").val();
loadAllCharts(year);
});
function loadChart(chart, endpoint) {
$.ajax({
url: endpoint,
type: "GET",
dataType: "json",
success: (jsonResponse) => {
// Extract data from the response
const title = jsonResponse.title;
const labels = jsonResponse.data.labels;
const datasets = jsonResponse.data.datasets;
// Reset the current chart
chart.data.datasets = [];
chart.data.labels = [];
// Load new data into the chart
chart.options.title.text = title;
chart.options.title.display = true;
chart.data.labels = labels;
datasets.forEach(dataset => {
chart.data.datasets.push(dataset);
});
chart.update();
},
error: () => console.log("Failed to fetch chart data from " + endpoint + "!")
});
}
function loadAllCharts(year) {
loadChart(salesChart, `/shop/chart/sales/${year}/`);
loadChart(spendPerCustomerChart, `/shop/chart/spend-per-customer/${year}/`);
loadChart(paymentSuccessChart, `/shop/chart/payment-success/${year}/`);
loadChart(paymentMethodChart, `/shop/chart/payment-method/${year}/`);
}
</script>
<div class="row">
<div class="col-6">
<canvas id="salesChart"></canvas>
</div>
<div class="col-6">
<canvas id="paymentSuccessChart"></canvas>
</div>
<div class="col-6">
<canvas id="spendPerCustomerChart"></canvas>
</div>
<div class="col-6">
<canvas id="paymentMethodChart"></canvas>
</div>
</div>
<script>
let salesCtx = document.getElementById("salesChart").getContext("2d");
let salesChart = new Chart(salesCtx, {
type: "bar",
options: {
responsive: true,
title: {
display: false,
text: ""
}
}
});
let spendPerCustomerCtx = document.getElementById("spendPerCustomerChart").getContext("2d");
let spendPerCustomerChart = new Chart(spendPerCustomerCtx, {
type: "line",
options: {
responsive: true,
title: {
display: false,
text: ""
}
}
});
let paymentSuccessCtx = document.getElementById("paymentSuccessChart").getContext("2d");
let paymentSuccessChart = new Chart(paymentSuccessCtx, {
type: "pie",
options: {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 1,
title: {
display: false,
text: ""
},
layout: {
padding: {
left: 0,
right: 0,
top: 0,
bottom: 25
}
}
}
});
let paymentMethodCtx = document.getElementById("paymentMethodChart").getContext("2d");
let paymentMethodChart = new Chart(paymentMethodCtx, {
type: "pie",
options: {
responsive: true,
maintainAspectRatio: false,
aspectRatio: 1,
title: {
display: false,
text: ""
},
layout: {
padding: {
left: 0,
right: 0,
top: 0,
bottom: 25
}
}
}
});
</script>
{% endblock %}
Next, create a core/admin.py file:
# core/admin.py
from django.contrib import admin
from django.contrib.admin.views.decorators import staff_member_required
from django.shortcuts import render
from django.urls import path
@staff_member_required
def admin_statistics_view(request):
return render(request, "admin/statistics.html", {
"title": "Statistics"
})
class CustomAdminSite(admin.AdminSite):
def get_app_list(self, request, _=None):
app_list = super().get_app_list(request)
app_list += [
{
"name": "My Custom App",
"app_label": "my_custom_app",
"models": [
{
"name": "Statistics",
"object_name": "statistics",
"admin_url": "/admin/statistics",
"view_only": True,
}
],
}
]
return app_list
def get_urls(self):
urls = super().get_urls()
urls += [
path("statistics/", admin_statistics_view, name="admin-statistics"),
]
return urls
Here, we created a staff protected view called admin_statistics_view
. Then, we created a new AdminSite
and overrode get_app_list
to add our own custom application to it. We provided our application with an artificial view-only model called Statistics
. Lastly, we overrode get_urls
and assigned a URL to our new view.
Create an AdminConfig
inside core/apps.py:
# core/apps.py
from django.contrib.admin.apps import AdminConfig
class CustomAdminConfig(AdminConfig):
default_site = "core.admin.CustomAdminSite"
Here, we created a new AdminConfig
, which loads our CustomAdminSite
instead of Django's default one.
Replace the default AdminConfig
with the new one in core/settings.py:
INSTALLED_APPS = [
"core.apps.CustomAdminConfig", # replaced
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"shop.apps.ShopConfig",
]
Next, update core/urls.py as follows:
# core/urls.py
from django.contrib import admin
from django.urls import path, include
from .admin import admin_statistics_view # new
urlpatterns = [
path( # new
"admin/statistics/",
admin.site.admin_view(admin_statistics_view),
name="admin-statistics"
),
path("admin/", admin.site.urls),
path("shop/", include("shop.urls")),
]
Navigate to http://localhost:8000/admin/ to view the new Django admin view.
The final result:
Click on 'View' to see the charts:
Extend an Existing Admin Template
You can always extend admin templates and override the parts you want.
For example, if you wanted to put charts under your shop models, you can do that by overriding the app_index.html template. Add a new app_index.html file to "shop/templates/admin/shop" then add the HTML found here.
Final result:
Conclusion
In this tutorial, you learned how to serve data with Django and then visualize it using Chart.js. We also looked at two different approaches for integrating charts into the Django admin.
Grab the code from the django-interactive-charts repo on GitHub.