What to do when you’re ready to level up the interactivity of your Django app, without having to fiddle around with jQuery? Do you have to build a full-fledged SPA, delete all your Django templates and reimplement everything with Django Rest Framework?
You don’t have to give up everything you know and love from your Django projects. In fact, you can get the interactivity and convenience of Vue.js, without all the overhead of a SPA setup.
Here’s a step-by step example, how you can switch from vanilla Django templates without JavaScript (or jQuery), towards adding progressive enhancements with Vue.
The Beginning
Let’s focus on the parts which are relevant to adding Vue.js into a Django project. We start with a basic Django view, and a corresponding template.
view.py:
import random
from django.shortcuts import render
def index(request):
names = ("bob", "dan", "jack", "lizzy", "susan")
items = []
for i in range(100):
items.append({
"name": random.choice(names),
"age": random.randint(20,80),
"url": "https://example.com",
})
context = {}
context["items"] = items
return render(request, 'list.html', context)
The view is very simple. It generates some dummy data - 100 dict entries are added to a list, and the list is passed on inside a context dictionary to the template. The name and age always vary due to some randomness.
list.html:
{% if items %}
<ul>
{% for item in items %}
<li>
<a href="{{item.url}}">{{ item.name }}</a> <button>Hey!</button>
</li>
{% endfor %}
</ul>
{% else %}
<p>No items available.</p>
{% endif %}
Inside the list.html
template, each item from the items
list
get rendered inside a for loop. Nothing fancy. There’s a button, but it’s
not doing anything.
Getting dynamic with Vue
How can we go from a basic template, to using Vue.js?
There’s a few steps we have to take, in the most simple case:
- Add the data into the template in JSON format.
- Add the Vue.js library into the template (like you would jQuery).
- Configure Vue to not interfere with the Django template syntax (both use double-curly-brackets as delimiters).
- Add Vue.js markup which does what we want.
First, render out the data
We don’t need to add a new endpoint to Django, or anything fancy.
The most simple approach will require only a simple modification
to our view.py file: we’ll need to import json
and save the items
list as a rendered JSON string into the context dictionary:
‚
import json
# ...
context = {}
context["items_json"] = json.dumps(items)
# ...
That’s it!
Sidenote: Will this work when using Class Based Views?
Yes, absolutely. As long as you can pass data to your template, you’ll be fine with either FBV or CBV. If you’re unsure about which style of views will be best for you, take a look at this article.
The template - a complete picture
The next change happens inside the template.
As the template is going to change quite a bit, I created a new one,
and called it vue_list.html
(don’t forget to make your view use the new one!):
Here’s the complete template, so you can see everything at once before diving into details. We’re going to go through all relevant parts of it step-by-step later on, don’t worry!
vue_list.html:
{% if items %}
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script type='text/javascript'>
var people = {{ items_json|safe }};
</script>
<div id="app">
[[message]]
<ul>
<li v-for="person in people">
<a v-bind:href="person.url">[[ person.name ]]</a> <button v-on:click="greet(person.name)">hey</button>
</li>
</ul>
</div>
<script>
var app = new Vue({
delimiters: ['[[', ']]'],
el: '#app',
data: {
message: 'Hello Vue!',
people: people,
},
methods: {
greet: function(name) {
console.log('Hello from ' + name + '!')
}
}
});
</script>
{% else %}
<p>No items available.</p>
{% endif %}
Once again, there’s a lot happening here. Let’s go through it step by step.
Add the Vue.js library
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
In the above line, we load Vue.js from a CDN. That’s all you have to do. That’s no more effort, than including jQuery into your template.
Render our JSON data as JavaScript
<script type='text/javascript'>
var people = {{ items_json|safe }};
</script>
Here, we render out the items_json
string without modifying it.
This way, we get a JavaScript variable people
which is being set to
the content of items_json
in a usable form.
The safe
filter prevents escaping of html-endangering characters,
which the Django
template would otherwise do by default. This way, the JSON string
is considered safe, and will be put into the template 1:1.
Here’s what it will approximately look like in the rendered page:
<script type='text/javascript'>
var people = [{"name": "bob", "age": 32, "url": "https://example.com"}, (...)]
</script>
The (...)
is where I got lazy typing any more. Just imagine 99 more entries like the first one in a single line.
Update: alternatively to the manual script
HTML block, you could take a look at the json_script
template tag here. Both are fine.
Add the HTML element which Vue will modify
<div id="app">
(...)
</div>
This is where vue will make changes. Inside it, we will specify all of our
vue templating. I left out the content in this case, and replaced it with
(...)
, as it doesn’t matter for now.
Configure Vue
The complete configuration of Vue happens in this script block:
<script>
var app = new Vue({
delimiters: ['[[', ']]'],
el: '#app',
data: {
message: 'Hello Vue!',
people: people,
},
methods: {
greet: function(name) {
console.log('Hello from ' + name + '!')
}
}
});
</script>
This is where it all comes together.
The most important part, are the delimiters. Vue normally uses the same syntax as Django templates to denote expressions to be replaced: two curly brackets.
We change those to double-square-brakcets, so Django templating leaves our Vue template code alone. This happens in the line:
delimiters: ['[[', ']]'],
Rember the people
variable we set in the very first script tag?
We are going to pass it on into Vue.
In addition, we also pass a message - a plain string, as in the basic
Vue tutorial.
data: {
message: 'Hello Vue!',
people: people,
},
The methods entry, defines a function which will be easily callable from inside of our Vue app. Let’s look at the Vue template to see where it’s invoked.
Time for Vue templating
The last part, happens inside the div which we chose as our app container. Once the page is loaded, and Vue comes to life, it will go into that element and replace template markup with actual values.
As you are familiar with the Django templating language, you won’t have a hard time getting started and understanding what happens here:
[[message]]
<ul>
<li v-for="person in people">
<a v-bind:href="person.url">[[ person.name ]]</a> <button v-on:click="greet(person.name)">hey</button>
</li>
</ul>
We render out the value of the message data entry, which we added to the Vue config.
We use v-for
to iterate over each entry in the people
array, and add a new
a
element for each. The loop content is limited to the content of the li
element.
Inside of the loop, we set the href attribute dynamically, render out the name of the person and add an on-click listener to the button. When the button is clicked, the function defined in the Vue object will be called with the person’s name.
All done!
That’s it already. If the example seems overwhelming, that’s okay. There’s a lot of stuff going on, and I made sure to include everything which you will need to get started.
If the Vue templating seems unclear, or you want to know more about Vue, I can’t recommend the official getting started guide enough. They are very thorough and a pleasure to work with.
In particular, you might want to take a look at: