Quick and Dirty Django – Passing data to JavaScript without AJAX

A quick & dirty approach for passing data from Django to JavaScript, without using AJAX.

Quick and Dirty Django is aimed at showing neat solutions, for when you just want to have something working.

*UPDATE* – Thanks to the folks in the comments for pointing out that this already exists in Django as json_script template tag, so check this out!

When we want to pass data from Django to JavaScript, we usually talk about APIs, serializers, JSON and AJAX calls. This is usually combined with the additional complexity of having a React or Angular running the frontend.

But sometimes you want to do something quick and dirty – plot a chart & don’t bother with the entire single page application infrastructure.

The naive approach

Let’s say we have a Django application with the following model:

from django.db import models


class SomeDataModel(models.Model):
    date = models.DateField(db_index=True)
    value = models.IntegerField()

And we have a simple TemplateView:

from django.views.generic import TemplateView


class SomeTemplateView(TemplateView):
    template_name = 'some_template.html'

Now, we’ve decided to plot a simple line chart, using Chart.js, and we want to avoid doing AJAX calls / exposing APIs / etc.

If we want to render a simple line chart in our some_template.html, the code is going to look like this (taken from their samples):

<canvas id="chart"></canvas>

<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js"></script>
<script>
window.onload = function () {
  var data = [48, -63, 81, 11, 70];
  var labels = ['January', 'February', 'March', 'April', 'May'];

  var config = {
    type: 'line',
    data: {
      labels: labels,
      datasets: [
        {
          label: 'A random dataset',
          backgroundColor: 'black',
          borderColor: 'lightblue',
          data: data,
          fill: false
        }
      ]
    },
    options: {
      responsive: true
    }
  };

  var ctx = document.getElementById('chart').getContext('2d');
  window.myLine = new Chart(ctx, config);
};
</script>

This will produce the following:

Now, if we want to chart data coming from SomeDataModel, we can approach it like that:

from django.views.generic import TemplateView

from some_project.some_app.models import SomeDataModel


class SomeTemplateView(TemplateView):
    template_name = 'some_template.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        context['data'] = [
            {
                'id': obj.id,
                'value': obj.value,
                'date': obj.date.isoformat()
            }
            for obj in SomeDataModel.objects.all()
        ]

        return context

And then, we render a JavaScript array using Django template:

<canvas id="chart"></canvas>

<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js"></script>
<script>
window.onload = function () {
  // We render via Django template
  var data = [
    {% for item in data %}
      {{ item.value }},
    {% endfor %}
  ]

  // We render via Django template
  var labels = [
    {% for item in data %}
      "{{ item.date }}",
    {% endfor %}
  ]

  console.log(data);
  console.log(labels);

  var config = {
    type: 'line',
    data: {
      labels: labels,
      datasets: [
        {
          label: 'A random dataset',
          backgroundColor: 'black',
          borderColor: 'lightblue',
          data: data,
          fill: false
        }
      ]
    },
    options: {
      responsive: true
    }
  };

  var ctx = document.getElementById('chart').getContext('2d');
  window.myLine = new Chart(ctx, config);
};

</script>

Now, this works, but it’s too dirty for my taste.

We no-longer have JavaScript, but rather JavaScript, mixed with Django templates.

We lose the opportunity to extract the JavaScript to a separate .js file. We also lose the opportunity to run prettier on that JavaScript.

We can do it better & still be quick about it.

Spicing things up

The strategy that we are going to follow is:

  1. In our view, serialize the data via json.dumps& store it in context.
  2. Render a hidden <div> element, with an unique id and data-json attribute equal to the serialized JSON data.
  3. In JavaScript, query that div, read the data-json attribute & use JSON.parse to get the data required.

This has the benefit of keeping the JavaScript code clean of any Django template language & also having a reusable pattern for getting the data we need.

It’s like doing an easier AJAX.

Here’s the example, following this strategy:

import json

from django.views.generic import TemplateView


class SomeTemplateView(TemplateView):
    template_name = 'some_template.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)

        context['data'] = json.dumps(
            [
                {
                    'id': obj.id,
                    'value': obj.value,
                    'date': obj.date.isoformat()
                }
                for obj in SomeDataModel.objects.all()
            ]
        )

        return context

Now, we’ll extract our JavaScript code to a static file called chart.js.

This gives us the following some_template.html:

{% load static %}

<div style="display: none" id="jsonData" data-json="{{ data }}"></div>

<canvas id="chart"></canvas>

<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js"></script>
<script src="{% static 'chart.js' %}"></script>

As you can see, the magic is happening in the hidden div. We hide the div to remove it from any layout.

Here, you can give it a proper id, or use whatever HTML element you find suitable.

The data-json attribute (which is arbitrary & not something predefined) holds the JSON we need.

Now, finally, we’ll implement the following simple function to get & parse the data we need:

function loadJson(selector) {
  return JSON.parse(document.querySelector(selector).getAttribute('data-json'));
}

And with that, our chart.js is ready:

function loadJson(selector) {
  return JSON.parse(document.querySelector(selector).getAttribute('data-json'));
}

window.onload = function () {
  var jsonData = loadJson('#jsonData');

  var data = jsonData.map((item) => item.value);
  var labels = jsonData.map((item) => item.date);

  console.log(data);
  console.log(labels);

  var config = {
    type: 'line',
    data: {
      labels: labels,
      datasets: [
        {
          label: 'A random dataset',
          backgroundColor: 'black',
          borderColor: 'lightblue',
          data: data,
          fill: false
        }
      ]
    },
    options: {
      responsive: true
    }
  };

  var ctx = document.getElementById('chart').getContext('2d');
  window.myLine = new Chart(ctx, config);
};

A quick & dirty pattern, for when you just want to ship something. This is the end result:

A disclaimer

I’ll not recommend using this approach for anything more than a quick & dirty proof of concept.

Once your JavaScript starts growing in size, it’ll be hard to keep it under control & the recommended approach is to go the full SPA way, using one of the popular frameworks (for us, that’s React)

✌️

Share This:
There are 4 comments

4 Responses to “Quick and Dirty Django – Passing data to JavaScript without AJAX”

Leave a Reply

Your email address will not be published. Required fields are marked *