Need help with your Django project?
Check our django servicesQuick and Dirty Django - Passing data to JavaScript without 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:
- n our view, serialize the data via
json.dumps
& store it in context. - Render a hidden
<div>
element, with an uniqueid
anddata-json
attribute equal to the serialized JSON data. - In JavaScript, query that div, read the
data-json
attribute & useJSON.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)
✌️