Let’s imagine this scenario, you’re building a Django project, everything going very well. At some point, you needed to level up the interactivity of your app. what are you gonna do?
You will probably achieve the desired behavior using jquery or you will keep your models, build an API, and use a JavaScript SPA for the frontend.
What if I told you that you can keep everything you build with Django, get the interactivity and convenience of Vue.js, without all the overhead of a SPA setup?
This post aims to show that you can start to use Vue with your Django projects immediately without any sophisticated setup that will take hours to complete.
A Demo App
For a demo, I made a simple todo app, so I can play around with vue.js alongside the Django template.
The app shows the users' tasks, and the user can perform basic crud actions.
It looks really simple, but I but it’s a great way to practice some of the key concepts of Vue.
Try to create it yourself, and of course, If you get stuck, you can always get back to my code.
⚡ github.com/aymaneMx/vuejs-alongside-django
Setup
If you check out the official Vue guide, they have links to a CDN where you can simply include Vue via a <script>
tag into your Django template:
<script src="<https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js>"></script>
<div id="vue-app">
[[ message ]]
</div>
var app = new Vue({
delimiters: ["[[", "]]"],
el: '#vue-app',
data: {
message: 'Hello Vue!'
}
})
That is it, we have already created our very first Vue app! Couple of things to notice here:
-
el
: stand for element, and it provides the Vue instance an existing DOM element to mount on. - Usually, we don’t need to define the limiters explicitly but here we need to because the default delimiters of Vue are the same as the default delimiter of Django, so we need to use something else for Vue and that’s why we’re using
[[ ]]
here instead of{{ }}
.
Access Django Data from Vue
The easiest way is to access a Django template variable from Vue, is by using the built-in Django json_script
filter.
{{ django_variable | json_script:"js-data" }}
Go check the documentation, it's a pretty cool way to outputs a Python object as JSON, wrapped in a <script>
tag, ready for use with JavaScript.
Unfortunately, This solution doesn't always work!
and that what happened to me when I tried to use the variable tasks
in the demo app:
# todo/views.py
def home_view(request):
tasks = Task.objects.all()
context = {
'tasks': tasks,
}
return render(request, 'home.html', context)
I get the following error!
Object of type QuerySet is not JSON serializable Django.
The way I solved this issue is by creating a task serializer,
# todo/serializers.py
from rest_framework import serializers
from todo.models import Task
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Task
fields = "__all__"
and I use it in my view:
from django.shortcuts import render
from todo.models import Task
from todo.serializers import TaskSerializer
def home_view(request):
tasks = Task.objects.all()
context = {
'tasks': TaskSerializer(tasks, many=True).data,
}
return render(request, 'home.html', context)
Consuming APIs
In the demo app, I was able to create, delete, update tasks, but only on the frontend side, nothing changed in the backend!
So I had to create a simple API that the Vue app can consume and display data from.
Next, I found myself googling how Vuejs consume APIs?
There are several ways to do so, but a very popular approach is to use Axios, which is also recommended in the official Vue Docs.
Same as Vue, You can include Axios via a script tag to your Django template.
<script src="<https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js>"></script>
To pass Django’s CSRF protection mechanism, Axios needs to include the respective cookie in its requests. To accomplish this is to set global Axios defaults:
<script>
axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
</script>
For example, let’s delete a task using Axios, assuming that /api/<pk>/delete/
is the right endpoint.
var url = '/api/' + task_id + '/delete/';
axios
.delete(url)
.then(response => {
this.deleteTask(task_id)
})
.catch(error => {
console.log(error);
});
This call can be done within a Vue instance’s mounted
hook or any other place where you can put JavaScript code.
All done!
That wasn’t so hard! Now you can focus on building cool things with Vue on top of an API driven by Django.
Top comments (2)
Sorry, so you still end up using an API, right? I am a noob, so please explain what is the point of this practice? Basically seems there's no way to have the easy binding of objects like DTL allows, right? So, the advantage this approach gives is simply not having to remove/rewrite all the old DTL templates, but since we have to write an API anyway, it seems the code becomes a mess this way, no? If we know BEFOREHAND that we will need interactivity of Vue (or any other frontend framework like that), this approach would be a mistake, right? In that case we should start without DTL, just start with DRF right away and build the frontend separately, correct?
I ask if my mental model of this is right, that's why every statement has a question at the end :D, since maybe I misunderstand or don't know something completely.
I have done this before. And it's amazing to use both for an amazing experience