I\'ve recently started on some social media web site using Django. Im using the default django template engine to fill my pages. But at this moment, I want to a
I looked at this question and others a while back while looking to do pretty much what the OP was asking. Unfortunately, most of the information on Vue is given in an SPA context. However, as Evan You has often repeated, Vue is not opinionated and does not require usage within an SPA.
I'd like to share some of my findings and sketch out a possible approach to making Django and Vue work together. While an SPA is not required I think the real power of Vue comes from its Components and that kinda pushes you towards a Webpack or similar approach, not simple standalone mode in the html.
Also, just to be very clear: 90%+ of my the contents Django view code and templates remained exactly the same as before. Django doesn't particularly care that it's using webpack_loader and render_bundle. And even less that render_bundle has something to do with Vue. Between Django template blocks
, verbatim
tags and Vue slots
, you get a lot of flexibility that allows you to leave most of your existing content alone.
Last, once your app is stable, you can disable the hotreload server on port 3000, run npm run build-production
and use collectstatic
to have your JS served via nginx/apache like any normal static content. So node
runs as the occasional batch rather than as a service. Takes a bit of fiddling with Django's config, but well within reason.
Apologies, this is quite sketchy, don't expect working code as I am stripping so much out. But hopefully it'll give you an idea.
mysite/__full12_vue.html:
This is my base Vue-ify Django template that extends my existing Django base template, __full12.html.
assume that __full12.html defines all the general Django blocks, like {% block content %} and the like
(actually, there is a very important div with ID bme-vue
so I've added this template at the end as well. )
I've added a Vue Component to display user messages.
And redefined the menu template to use Vue + Bootstrap dropdowns.
{% extends "mysite/__full12.html" %}
{% load render_bundle from webpack_loader %}
{% block nav %}
{% include "mysite/menu_vue.html" %}
{% endblock nav %}
{% block user_msg %}
THIS SHOULDNT APPEAR ON SCREEN IF VUE WORKED
{% endblock user_msg %}
{%block extra_js_body_end%}
{% render_bundle bundle_name %}
{%endblock extra_js_body_end%}
webpack.config.development.js:
This is where you tell Webpack which JS to serve for the bundle_name you specify in the view.
How to configure Webpack is out of scope of my post, but I can assure you it was a real PIA. I started out with pip django-webpack-loader, then https://github.com/NdagiStanley/vue-django then Bootstrap 4 stuff. However, the end result is way more powerful than standalone, in my opinion.
/*
webpack config dev for retest
*/
config.entry = {
"main" : [
'webpack-dev-server/client?http://localhost:3000','../../static_src/js/index'],
// ....stuff..
//KEY: ONE entrypoint for EACH bundlename that you use.
"mydjangoappname/some_django_view" : ["../../static_src/js/mydjangoappname/some_django_view"],
"mydjangoappname/another_django_view" : ["../../static_src/js/mydjangoappname/another_django_view"],
// ....stuff..
}
// ....stuff left out...
mydjangoappname/some_django_template.html
Finally, we are ready to display some actual contents:
bme-nav-item
and bme-tab-pane
are 2 custom Vue components I use for Boostrap 4 tab navs and contents.
Django uses var settings= some-json-object
to communicate instance-specific, rather than page-generic, data to Vue and JS
{% extends "mysite/__full12_vue.html" %}
{% block content %}
mydjangoappname/some_django_view.js:
import Vue from 'vue';
import Vuex from 'vuex';
//now Vue is using Vuex, which injects $store centralized state variables as needed
Vue.use(Vuex);
//KEY: re-using components defined once.
import {base_messages, base_components} from '../mysite/commonbase.js';
var local_components = {
//nothing, but I could have imported some other components to mix-n-match
//in any case, bme-nav-item, bme-tab-pane and bme-user-messages need to
//coming from somewhere for this page!
};
const components = Object.assign({}, base_components, local_components);
//we're going to put together a Vue on the fly...
export function dovue(config) {
//KEY: The store is a Vuex object - don't be fooled, it's not SPA-only
// it's the easiest way to coherently share data across Vue Components, so use it.
store.commit('initialize', config);
//here I am telling the store which html IDs need hiding
var li_tohide = settings.li_tohide || [];
li_tohide.forEach(function(hidden) {
store.commit('add_hidden', hidden);
});
/* eslint-disable no-new */
var vm = new Vue({
//KEY: This tells the Vue instance what parts of your html are in its scope.
el: '#bme-vue'
//KEY: each bme-xxx and bme-yyy special tag needs to be found in components below
//otherwise you'll see my SHOULDNT APPEAR IF VUE WORKED text in your page
,components: components
,data: {
li_rowcount: window.settings.li_rowcount || []
,csrf_token: window.csrf_token
,url_batch: "some url"
}
,mounted: function () {
// a Vue lifecycle hook. You could use to set up Vue Event listening for example
console.log("data.js.lifecycle.mounted");
}
,methods : {
,run_batch: function(e) {
//hook this up to a button
console.assert(this.$data, COMPONENTNAME + ".run_batch.this.$data missing. check object types");
var url = e.target.dataset.url
//this is defined elsewhere
post_url_message(this, url, this.csrf_token);
}
}
//the Vuex instance to use for this Vue.
,store: store
});
//did Django provide any user messages that need displaying?
var li_user_message = config.li_user_message || [];
li_user_message.forEach(function(user_message, i) {
//the bme-user-messages Component? It's listening for this event
//and will display Bootstrap Alerts for them.
vm.$emit(base_messages.EV_USER_MESSAGE, user_message);
});
return vm;
}
//various app and page specific settings...
import {app_config, LOCALNAV_LINK, TOPNAV_LINK_OTHERS} from "./constants";
var page_config = {
//used to show which navigation items are .active
localnav_link : LOCALNAV_LINK.data
, topnav_link: TOPNAV_LINK_OTHERS.system_edit_currentdb
};
//this is coming from Django's RequestContext.
var generated_config = window.settings;
//ok, now we are merging together a Django app level config, the page config and finally
//what the Django view has put into settings. This will be passed to the Vuex store
//individual Vue Components will then grab what they need from their $store attribute
//which will point to that Vuex instance.
var local_config = Object.assign({}, app_config, page_config, generated_config);
var vm = dovue(local_config);
vuex/generic.js:
And a naive, mostly read-only store implementation:
//you can add your page's extra state, but this is a shared baseline
//for the site
const state = {
active_tab: ""
,topnav_link: ""
,localnav_link: ""
,li_user_message: []
,s_visible_tabid: new Set()
,urls: {}
};
const mutations = {
//this is what your page-specific JS is putting into the state.
initialize(state, config){
//initialize the store to a given configuration
//KEY: attributes that did not exist on the state in the first place wont be reactive.
// console.log("store.initialize");
Object.assign(state, config);
},
//a sample mutation
set_active_tab(state, tabid){
//which bme-tab-nav is active?
if (! state.s_visible_tab.has(tabid)){
return;
}
state.active_tab = tabid;
},
};
export {state as generic_state, mutations};
and to give you an idea of the general file hierarchy:
.
./manage.py
./package.json //keep this under version control
./
├── mydjangoappname
│ ├── migrations
│ └── static
│ └── mydjangoappname
├── node_modules
├ //lots of JavaScript packages here, deposited/managed via npm && package.json
├── static
│ └── js
├── static_src
│ ├── assets
│ ├── bundles
│ │ // this is where django-webpack-loader's default config deposits generated bundles...
│ │ // probably belonged somewhere else than under static_src ...
│ │ ├── mydjangoappname
│ ├── components
│ │ ├── mydjangoappname
│ ├── css
│ ├── js
│ │ ├── mydjangoappname
│ │ └── mysite
│ └── vuex
│ ├── mydjangoappname
├── staticfiles
│ // for Production, collectstatic should grab django-webpack-loader's bundles, I think...
├── templates
│ ├── admin
│ │ └── pssystem
│ ├── mydjangoappname
│ └── mysite
└── mysite
├── config
├ // where you configure webpack and the entry points.
├ webpack.config.development.js
├── sql
│ └── sysdata
├── static
│ └── mysite
└── templatetags
OK, I did have to modify the site's base template to make sure that div#bme-vue is always available.
Probably a bit of refactoring needed between this and mysite/__full12_vue.html.
mysite/__full12.html:
...more blocks...
{% block search %}
{% endblock search %}
{% block content %}
{% endblock %}
...more blocks...
{%block extra_js_body_end%}
{%endblock extra_js_body_end%}