How to customize Django Admin index page and add a chart ?

Custom Django admin index

April 2020

The purpose of this tutorial is to demonstrate how to customize the Django admin index page and add a chart easily using the library django-chartJs.

You can find the source code in this repository

For this tutorial will use a model Product

class Product(models.Model):
    user = models.ForeignKey(User, null=True, blank=True, on_delete=models.CASCADE)
    refShop = models.ForeignKey(Shop, db_index=True,on_delete=models.CASCADE)
    title = models.TextField(max_length=65)
    price = models.FloatField(default=0)
    withEndDate = models.BooleanField(default=False)
    endDate = models.DateField(blank=True,null=True)
    description = models.CharField(max_length=65, default='', null=True, blank=True)
    createdAt = models.DateTimeField(auto_now_add=True)
    updatedAt = models.DateTimeField(auto_now=True)
    available = models.BooleanField(default=True)

And our goal will be to display a custom admin index page with a graph showing the number of new product per month.

Custom Django admin index

To do that we will tell Django that we want to use a custom index html file. As we did on this tutorial , we add a new line index_template in our Admin class with the path to our new file

from django.contrib.admin import AdminSite
class MyAdminSite(AdminSite):
    login_template = 'backoffice/templates/admin/login.html'
    index_template = 'backoffice/templates/admin/index.html'

Then we will override the default Django index.html file by copying it into our new path

 cp ../../../environnements/genericenv/lib/python3.7/site-packages/django/contrib/admin/templates/admin/index.html backoffice/templates/admin/

To display a chart we will use the django-chartjs librarie so we include it in our requirements.txt file

django==3
django-chartjs

And then add it to our INSTALLED_APPS of our settings.py file

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'backoffice',
    'chartjs',
]

Now in the {% block content %} of the index.html file we will add our code for displaying a graph

{% block content %}
 <canvas id="myChart" width="50" height="10"></canvas>

<script type="text/javascript" src="https://code.jquery.com/jquery-1.10.0.min.js"></script>
<script type="text/javascript" src="{% static 'js/Chart.min.js' %}"></script>
<script type="text/javascript">
   $.get('{% url "new_product_per_month" %}', function(data) {
                var ctx = $("#myChart").get(0).getContext("2d");
                new Chart(ctx, {
                    type: 'line', data: data
                });
   });
</script>

<div id="content-main">

To get the data for the graph to being displayed we call a view with the instruction .get(‘{% url “new_product_per_month” %}’

So we need to define this view in our views.py file

from chartjs.views.lines import BaseLineChartView
from django.db.models import Count
from django.views.generic import TemplateView

class NewProductMonthChartJSONView(BaseLineChartView):
    def get_labels(self):
        """Return 7 labels for the x-axis."""
        return ["January", "February", "March", "April", "Mai", "June", "July","August","September","October","November","December"]

    def get_providers(self):
        """Return names of datasets."""
        return ["New products"]

    def get_data(self):
        """Return 3 datasets to plot."""
        #get user per month
        from django.db.models import Count
        from django.db.models.functions import TruncMonth
        from backoffice.models import Product
        import datetime
        date = datetime.date.today()
        items = Product.objects.filter(createdAt__year=date.year).annotate(month=TruncMonth('createdAt')).values(
            'month').annotate(total=Count('id'))
        totalMonth={}
        #initialisation
        for i in range(1, 13):
            totalMonth[i]='0'
        for item in items:
            month = item["month"]
            totalMonth[month.month]= item["total"]
        return [[int(totalMonth.get(1)), int(totalMonth.get(2)), int(totalMonth.get(3)), int(totalMonth.get(4)), int(totalMonth.get(5)), int(totalMonth.get(6)), int(totalMonth.get(7)),int(totalMonth.get(8))
                    ,int(totalMonth.get(9)),int(totalMonth.get(10)),int(totalMonth.get(11)),int(totalMonth.get(12))]]

line_chart = TemplateView.as_view(template_name='line_chart.html')
line_chart_json = NewProductMonthChartJSONView.as_view()

This view defines 3 methods :

get_labelsReturn 12 labels for the x-axis.
get_providersReturn names of datasets.
get_dataReturn datasets to plot ie our new product per month count

Before providing data to our chart, we need to calculate the values

We make a query which will count the product created per month for the current year

 items = Product.objects.filter(createdAt__year=date.year).annotate(month=TruncMonth('createdAt')).values(
            'month').annotate(total=Count('id'))

Then we can send back a JSON which will be rendered by the graph. To get more details about the django-chartjs don’t hesitate to read the library documentation.

Christophe Surbier