## DEV Community is a community of 581,225 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

# Vue on Django, Part 4

Ryan Palo Updated on ・1 min read

This is Part 4 of my tutorial for making a Vue app powered by a Django REST backend. Finally! We've done it. You've done it. But most importantly, I've done it. 😊 In the first part, we set up just the Vue side. In Part 2, we set up the data model and got it so we could create and clear our Todos in the browser. In Part 3, we set up the Django backend. Part 4 will consist of giving our front end a way to talk to our backend, so that our todos can be persisted for all time. This section will make some API calls, so it would be useful to you to have some kind of knowledge of HTTP requests and JavaScript promises. If you're not familiar with these, you should at least be comfortable shrugging, following my lead, and Googling later.

## 0. Setting Up

We should only need one other thing installed for this part: Vue Resource, which helps us to make HTTP requests.

EDIT 11/21/17: Apparently they've depreciated Vue Resource. See this article on possible alternatives. Thanks @williezh! That being said, this tutorial should still work.

$npm install -s vue-resource  That's it! Let's get into the changes, shall we? ## 1. The Client-Side API Utility Add a file called api.js to your src/store folder. Here we go: // src/store/api.js import Vue from 'vue' import VueResource from 'vue-resource' Vue.use(VueResource) export default { get (url, request) { return Vue.http.get(url, request) .then((response) => Promise.resolve(response)) .catch((error) => Promise.reject(error)) }, post (url, request) { return Vue.http.post(url, request) .then((response) => Promise.resolve(response)) .catch((error) => Promise.reject(error)) }, patch (url, request) { return Vue.http.patch(url, request) .then((response) => Promise.resolve(response)) .catch((error) => Promise.reject(error)) }, delete (url, request) { return Vue.http.delete(url, request) .then((response) => Promise.resolve(response)) .catch((error) => Promise.reject(error)) } }  This file just exports an object with the HTTP methods we'll be using. This way, you can call it via api.post(stuff). You'll see an example of this. Keep in mind, that this section uses a lot of what I think is JavaScript promises, and I'm a little foggy on the inner workings of these so far. It's on the list to read more in-depth about. We're also going to update our store.js file to use these new methods in our actions. Remember, actions are allowed to be asynchronus, but mutations must be synchronus. This is why we do our API calls from within the actions, and it's a big part of why actions exist at all! // src/store/store.js import Vue from 'vue' import Vuex from 'vuex' import api from './api.js' Vue.use(Vuex) const apiRoot = 'http://localhost:8000' // This will change if you deploy later const store = new Vuex.Store({ state: { todos: [] }, mutations: { // Keep in mind that response is an HTTP response // returned by the Promise. // The mutations are in charge of updating the client state. 'GET_TODOS': function (state, response) { state.todos = response.body }, 'ADD_TODO': function (state, response) { state.todos.push(response.body) }, 'CLEAR_TODOS': function (state) { const todos = state.todos todos.splice(0, todos.length) }, // Note that we added one more for logging out errors. 'API_FAIL': function (state, error) { console.error(error) } }, actions: { // We added a getTodos action for the initial load from the server // These URLs come straight from the Django URL router we did in Part 3 getTodos (store) { return api.get(apiRoot + '/todos/') .then((response) => store.commit('GET_TODOS', response)) .catch((error) => store.commit('API_FAIL', error)) }, addTodo (store, todo) { return api.post(apiRoot + '/todos/', todo) .then((response) => store.commit('ADD_TODO', response)) .catch((error) => store.commit('API_FAIL', error)) }, clearTodos (store) { return api.delete(apiRoot + '/todos/clear_todos/') .then((response) => store.commit('CLEAR_TODOS')) .catch((error) => store.commit('API_FAIL', error)) } } }) export default store  There is a lot of change in this file, but this is really the meat of it. Everything else from here on out is really just book-keeping. Keep in mind that because of the way we set everything up, we are able to hook our app up to a back-end database and we aren't even going to touch the Components at all! That is the neatest part I think. We're also going to add one line to our main.js file, right at the bottom. When our app loads up, the last thing we want it to do before the client sees it is load up the todos array with the saved todos. // src/main.js import Vue from 'vue' import App from './App' import store from './store/store.js' /* eslint-disable no-new */ const v = new Vue({ el: 'body', store: store, components: { App } }) // This should be the only new line *** v.$store.dispatch('getTodos')



There's just one more thing that we should do to make our lives easier. Open up the file format_index_html.py. There's a few typos here from the vue project template that will make our life hard. Here's the fixed version. It's pretty much the same with some quotation marks added.

import sys
import fileinput

file = 'templates/index.html'

with open(file, "r+") as f:
f.seek(0)
f.write("{% load staticfiles %}\n" + s)

for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('href=//', "href=\"{% static '"))
for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('css', "css' %}"))
for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('src=//', "src=\"{% static '"))
for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('js', "js' %}\""))


Again, this code came with the vue template, and it's pretty ok (maybe not my favorite). But it's already written for us, so we're going with it.

That should just about do it. Here's how we kick the whole thing off.

1. Make sure your virtual environment is active.
\$ source .venv/bin/activate
# Windows: .venv\Scripts\activate

1. Run ./deploy.sh. If it complains about permissions, either chmod the permissions or just run bash deploy.sh. On windows, you should be able to run all of the steps in the deploy script manually. The only one you should change is running python manage.py runserver 8000 instead of doing it in two steps.

2. Watch all of the output closely. If there are any errors, you'll see them in this deluge of output.

3. Head over to localhost:8000 and enjoy!

## 2.5 Debugging

If it doesn't work, don't panic. Check your browser console. Errors there? Errors of a 500 nature are most likely server side. You're going to want to work on your django-rest app. If the errors are on your javascript side, kill any running dev servers and run npm run dev to run the vue server standalone. This won't have access to any server functions, but it will be easier to find the real error message.

## 3. Wrap Up

This has been a long one, and hopefully I didn't miss anything. Since I spaced the posts out (which I regret), I had to play some games with reminding myself what changed from Part to Part. So, if something doesn't work or is broken, let me know and I'll see if I can find where I went wrong. I added my final project folder on GitHub so you can search for discrepancies to aid in debugging. Thanks for sticking with me!

## Discussion (32)

electrocnic

Thank you a lot for this tutorial series, they help me a lot getting started with web-development from the beginning!

However, I had some problems, which you did not cover (I wonder why you did not have the same problems? I had them on Windows, did not test the tutorial part 4 on Ubuntu yet), especially with the template-part:

First, when I tried to run the server straight forward after doing 1:1 what you did (except that I used axios), there was a blank page and the console said error 404 file .../static/css not found.
I googled for a few hours and eventually managed to figure out that the files config/index.js and format_index_html.py where messing around with the static file paths of the generated index.html.
I also discovered strange artifacts in the final index.html using your format_index_html.py code: I truly believe, that the "' %}" in the css and js lines are messing up the final index.html completely, at least it has a completely broken syntax when I open the index.html in IntelliJ and improve the formatting with "Reformat Code". When I removed the mentioned symbols from the two lines so that the format_index_html.py now looks like the following, I got a valid index.html instead:

import sys
import fileinput

file = 'templates/index.html'

with open(file, "r+") as f:
f.seek(0)
f.write("{% load staticfiles %}\n" + s)

for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('href=//', "href=\"{% static '"))
for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('css', "css"))
for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('src=//', "src=\"{% static '"))
for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('js', "js"))


Also, I needed to modify the file vuedj/settings.py a bit, so that the statics-part looks like this:

STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

STATIC_URL = '/static/'


STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static-vuedj'),
)

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

STATIC_URL = '/staticfiles/'


However, I am getting a strange webpack-copy error then:

[copy-webpack-plugin] ERROR in [copy-webpack-plugin] unable to locate 'C:\path\to\my\project\static' at 'C:\path\to\my\project\static'


for which I could not find a solution so far. This prevents the files from proper translation, which results in runtime errors where the browser is complaining that it cannot recognize "import"-statements in javascript.

Do you have any idea how to fix that?
I already tried to read about webpack and the configuration of webpack. I just figured out that the difference between npm run dev and npm run build is, that statics and static file copies are only used at npm run build, which is the reason why I did not get errors during npm run dev.

Ryan Palo

Hmmm... I’m not sure. I wrote this post a good while ago, but I do remember that the vue Django starter repo probably ended up causing more problems than it solved. I’ll take a look at the error you’re talking about and see what I can do.

Thanks for sticking with it and reaching out!

Ryan Palo

From your code snippets, my first guess is that you should probably look at what your Django config’s static_root is set to. See these docs. Specifically the Deployment section. Make sure that’s outputting to the right place. Let me know if this solves it, but otherwise I’ll look deeper as soon as I can.

electrocnic

Thank you for the quick response!! I'll try it out, and I am curious. I played around with these static settings for quite a time already, and am not yet 100% sure what the right settings should be, so this is definitely a good start :)

Ryan Palo

No problem! OK, here's what I've got so far. On a fresh computer (windows, no less!), here were my steps:

1. I cloned the completed example repo.
2. I created a python virtual environment: py -3.6 -m venv .venv. You could probably also use python36 -m venv .venv or just python3 -m venv .venv depending on the versions of Python you have.
3. I installed the JavaScript requirements: npm install
4. I ran the build: npm run build
5. Since I'm on windows, I had to manually run each of the steps in deploy.sh manually. First was python format_index_html.py.
6. As you noticed earlier, format_index_html.py has some issues. I went into /templates/index.html manually and fixed the script tag to have these contents: <script type=text/javascript src="{% static 'app.7ff6b55ccbb5cd067a3d.js' %}"></script>. I think it was just some punctuation tweaks. Your app..js will be slightly different each time.
7. I installed the Python requirements: pip install -r requirements.txt
8. I ran collectstatic, which grabs your bundled app and puts it in your static directory: python manage.py collectstatic --noinput
9. I ran the server: python manage.py runserver

Everything worked like I expected with no import errors.

Some things that might have gone wrong for you:

In my vuedj/settings.py, I've got:

STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)

STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')

STATIC_URL = '/staticfiles/'


I notice that the STATIC_URL is different than what you said you're using. Give that a try.

I also don't see the copy-webpack-plugin in my package.json for that repo. Are you using the same versions as me? It wouldn't surprise me if you weren't because my package.json is probably out of date with today's JS standards. Compare with my package.json to be sure.

Ryan Palo

By the way, I've got some ideas in the works for writing a new set of articles that are updated for more modern times: Vue 2, vue-cli, Django 2, etc. I'm not sure when I'll get time to work on them, but I know that these articles and the library versions used here are slowly becoming obsolete.

electrocnic

Really nice, that you tried to reproduce my setup, and that you reply so fast! Appreciate that!

You said, on windows, one has to perform the deploy.sh script manually step by step? Why? I did not do that before, and it seems it does not make any difference for me? I use the git-bash (MINGW64) for windows. I can use shell-scripts like on any other linux system and shell-commands as well.

I also believe that I now, thank your link and your kick into the right direction, understand how the format_index_html.py and Jinja is supposed to work and to look like.

For me, neither the original, nor your version of this script made sense.
npm run build would produce a index.html which already has the links to static files and they look fine. I modified the script so that it adds the Jinja syntax correctly IN MY CASE. I really cannot tell why it is different for me:

import sys
import fileinput

file = 'templates/index.html'

with open(file, "r+") as f:
f.seek(0)
f.write("{% load staticfiles %}\n" + s)

for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('href=/staticfiles/', "href=\"{% static '"))
for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('.css', ".css' %}\""))
for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('src=/staticfiles/', "src=\"{% static '"))
for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('.js', ".js' %}\""))


What exactly did I do:
The original index.html template looks like this in my case:

<!DOCTYPE html>
<html>
<meta charset=utf-8>
<meta name=viewport content="width=device-width,initial-scale=1">
<title>test</title>
<body>
<div id=app></div>
<script type=text/javascript src=/staticfiles/js/vendor.cd8054bf01aa59402ea6.js></script>
<script type=text/javascript src=/staticfiles/js/app.a1073c350b2a8c23a52c.js></script>
</body>
</html>


... but now we want to add some {% here %} and some {% there %} to be able to inject the correct static paths dynamically if I understood that right?
Looking at the docs, how the final index.html should look like, I needed to modify the double-slash at href=// to href=/staticfiles/ and as you see I also needed to add "staticfiles" in order to be replaced as a whole by the second parameter, which I could leave as it was in your version. Then I needed to add a "." at the "css" and "js" modifiers, else it would mess up the path like this:

/staticfiles/css/app.2d31a09d9567f33bfd9cc48d15d26790.css


would become:

/staticfiles/css' %} /app.2d31a09d9567f33bfd9cc48d15d26790.css' %}


which was really weird and at the beginning I did not understand where this mess came from.

My fix produces:

{% load staticfiles %}
<!DOCTYPE html>
<html>
<meta charset=utf-8>
<meta name=viewport content="width=device-width,initial-scale=1">
<title>test</title>
<link href="{% static 'css/app.2d31a09d9567f33bfd9cc48d15d26790.css' %}" rel=stylesheet>
<body>
<div id=app></div>
<script type=text/javascript src="{% static 'js/manifest.7fd0cdf336adc5dd3e7b.js' %}"></script>
<script type=text/javascript src="{% static 'js/vendor.cd8054bf01aa59402ea6.js' %}"></script>
<script type=text/javascript src="{% static 'js/app.a1073c350b2a8c23a52c.js' %}"></script>
</body>
</html>


...which you probably also had in your case? Else I would not be able to understand how your app would have ever worked?

It makes sense and looks correct.

However, now I am getting the 404 not found at just one file, others are correctly loaded. I did not get the weird webpack error anymore, but the page is not visible anymore :D

This guy has the same problem and eventually found a solution for himself, I will try that approach tomorrow:
stackoverflow.com/questions/308575...

Really thank you so much for your fast replies, they really helped a lot, and I am indeed interested in Vue 2, vue-cli tutorials as well :D

Ryan Palo

I'm glad that you're headed in the right direction. Let me know if you have any other questions! :)

electrocnic

FUCK

Sorry, but I just wrote 30minutes of well-formatted documentation for my 3 bugfixes, which was deleted as I accidentely clicked "reload" on this page :(

Now I want at least share a short docu for my main 3 bugs, which I was able to solve, for anybody who comes after me and might have the same struggles:

1st: I installed the newest vue-cli:

npm uninstall vue-cli -g
npm install -g @vue/cli


2nd: I fixed the format_index_html.py like described in the comment above, but here is the full file again:

import sys
import fileinput

file = 'templates/index.html'

with open(file, "r+") as f:
f.seek(0)
f.write("{% load staticfiles %}\n" + s)

for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('href=/static/', "href=\"{% static '"))
for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('.css', ".css' %}\""))
for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('src=/static/', "src=\"{% static '"))
for i, line in enumerate(fileinput.input(file, inplace=1)):
sys.stdout.write(line.replace('.js', ".js' %}\""))



Will fix the broken index.html template and thus will also fix half of the issues with static files, to be correctly served at runtime.

3rd: Fixed the CopyWebpackPlugin error ERROR in [copy-webpack-plugin] unable to locate '[...]/static' at '[...]/static':
Therefore, a workaround in the build/build.js was necessary. All it does is adding a .gitkeep to the STATIC_ROOT directory before the build.

...

//add these lines to the other imports:
const webpackConfig = require('./webpack.prod.conf')
const mkdirp = require('mkdirp')
const fs = require("fs")

...

// add the mkdirp function in the rm-callback:
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
mkdirp(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
// path exists unless there was an error
fs.closeSync(fs.openSync(path.join(config.build.assetsRoot, config.build.assetsSubDirectory, '.gitkeep'), 'w'));
});

webpack(webpackConfig, function (err, stats) {
spinner.stop()

...


4th: I had the following bug at runtime, when I tried to add TODO-Entries:
TypeError: Cannot read property 'push' of undefined

and fixed it by modifying the response.body to response.data in the store/index.js (I renamed that file from store.js to index.js):

...

'GET_TODOS': function (state, response) {
state.todos = response.data
},
state.todos.push(response.data)
},

...


Hopefully this helps other people who might have the same struggles.

Thanks again for the nice tutorial, maybe I will see other tutorials by you, Sir! :)

Ryan Palo

Nicely done! I’m sure a lot of people will appreciate this.

Don

Ryan,
Very nice tutorial and very useful. I'm in the throes of "duplicating" what you did to build my own toolchain (Python/Django/Vue/PyCharm/Anaconda and a few other things) and it's a bit of a beast.

The only thing I may have missed was the creation of a "production" instance of the front end to run without using 'npm run dev' and a corresponding 'manage.py collectstatic' or am I missing something. Maybe that's all hidden under the '/deploy' [I'm on Win10 :-( ]?

Total aside: I'm a retired geek trying to stay somewhat current. Best of luck with your teaching goal. We need good teachers!
--Don

Ryan Palo

Hi! Thanks so much for reading, I’m glad it’s helping. There have been a lot of changes in the ecosystem since I wrote this, and I should probably go back and refresh it for new versions of the libraries (Django 2.0!). In theory, for production, you should build your JavaScript app into a bundle to be served as static files by your Django server. Does that help? Let me know if you need more guidance and I’ll see if I can write something more specific up. 😃 this stuff is hard, so don’t worry!

Don

Ryan,
Just a bit of a follow-up. I've gone down the Vue rat hole pretty deeply and am in the throes of some pretty hard (at least for me) problems. Don't know if it is something you might want to develop a tutorial on, but I thought I'd give you the idea.

The first issue I ran into was the decision about the structure of my Vue/Vuex app. Do I start with the small and simple and attempt to grow/refactor as my problem grows or do I build a full blown component/module structure as a frame and fill it in. I chose the latter (in retrospect, maybe wrongly) on the theory that once I had the frame, it would be more straightforward to build everything else and I'd avoid a lot of rework later on.

After some false starts, I decided to set up the vuex.js examples (github.com/vuejs/vuex) and model mine on the shopping cart version. The approach worked well as I could do side-by-side editing and adapting.

The issue I've run into and the place where I think a tutorial would be very useful is in understanding the various ways to reference/call from module/component to another module/component. Specifically, most of the existing examples are pretty good about references using the operations within the html, but I've really struggled to be able to reference actions in one module from methods in another, from pure js (not in an action/method/data/...) in one module to elements in another. The issue seems to revolve around how Vue/Vuex and webpack set things up, but I've really about broken my pick trying to connect things.

Anyway, I may have fallen in my own traps, but I thought it might be a place where you could do a useful tutorial if you are so inclined.

Good luck,
--Don

Ryan Palo

OK, that's some great insight. I'll be sure to try to get that figured out and worked in. Thanks for grinding through all of the tough stuff! I hope it starts to get easier!

Don

I just discovered that 'mixins' apparently implement the functionality I've been struggling to create. Might be useful when you do your update.

Steve Mayne

Thanks for taking the time to write this tutorial Ryan; hugely appreciated!

Ryan Palo

I’m glad you liked it. I think there’s still a lot of room for improvement as I keep learning Vue, but it’s hopefully a good jumping off point 😬

Steve Mayne

There is doubtless much that can be improved, but you've moved me on from "I must learn some front-end framework! I've been meaning to start for a couple of years..."

So thank you, from a traditionally back-end developer who doesn't get to play with user interfaces very much!

By the way, your writing style is superb. I wish you every success in your quest to learn everything :-)

Ryan Palo

Oh man that’s so nice, thanks! I’m glad I could help out. Good luck!

ｉｏ • Edited

Thank you very much for the tutorial!
Do you have any idea on how I could implement Vue 2 in my already existing Django blog?
It is not very clear to me how to handle content rendering. Am I using Django templates? Do I include my Vue components in my templates?
It's all blurry :-))

Ryan Palo

No problem. There are a couple ways to go. If you want Vue to handle the whole thing, then you can basically write the whole template in vue and just have Django serve a barebones template that just serves the vue JavaScript. If you just want vue to handle a smaller piece of the page that needs to be more responsive/interactive, then you can do the majority of the template in Django and source the js for the vue template where you need it. Don’t forget that at the end of the day, vue is just JavaScript, so you can treat it like any other vanilla JavaScript you are adding to your site. Does that help at all?

ｉｏ

Hmmm, not sure I fully see it now, I need to tinker with it a bit. Thank you for replying!

Ryan Palo

One thing that helped me was when I realized that I could simply include Vue as a script tag and have some of my templating logic live right on the same HTML file as my page layout. Here's another good resource that has a nice ramp-in. Hopefully that helps. Let me know what other questions you have, and hopefully I can help :)

ｉｏ

Okey dokey, will check it out. i am onto something. Will tell you how it goes when I have something ready-ish

Ryan Palo

Definitely do. I'm excited to see how it turns out!

annapamma

Okay, I just finished the whole tutorial, and I wanted to say thank you again!! I signed up for dev.to just to thank you! I also wanted to let you know that I work for a cancer research lab at a large institution, and your post is helping me build a web app to assist biologists with their research. So once again, thanks! :)

Ryan Palo

Oh my gosh! That's so amazing! Well, let me know if there's any way I can help. Also, let me know if you figure out a better way to do things. I wrote this as I was first learning Vue, so I'm sure there are ways to improve. Or even better, share a blog post with your brand new Dev.to account! :)

VidJa

Thanks too. I'm also using your vue tutorial in a scientific context. I want to revamp my old django site on microbial genomes to a vue.js app.

b.t.w. I found the following post helpful in preventing cors errors during debugging.
stackoverflow.com/questions/357609...

I'm looking forward to see an update using axios

electrocnic

If you wonder why it does not work on windows to run the deploy.sh:

• Use the git-bash for windows
• Insert this line as the very first line into the deploy.sh:
#!/bin/sh

• chmod 755 deploy.sh and you should be able to run it in the git bash with
• ./deploy.sh
Ryan Palo

Thanks for the tip! I do know why it doesn't work, I just don't like to use git-bash on this particular machine. I really actually like using PowerShell :)

Willie Zheng

It is pleasant to read your blog. But I was suggested to use axios instead of vue-resource.

Ryan Palo

Thanks! You are correct. I just looked and vue-resource has been depreciated. I'll update the article. Thanks!