Zephyrnet Logo

How to Prototype a Web App with Django and Vue.js

Date:

Wouldn’t it be cool if you could prototype a custom web application that’s responsive (mobile-ready), reactive (light-speed fast), with a full-featured admin interface to manage the content — all in no time? Actually, using Django and Vue.js, you can! 😁

0. Introduction: Full Stack Development 101

I’ll cover how to prototype a custom web app, and to keep it as short and sweet as possible, the context information here will be rather brief. However, I will provide — hopefully — enough resources so that you know where to go should you need more information.

To this end I’ll fully mash up data management, presentation, and routing between Django and Vue.js — so get ready!

About Django

You may know of Django, the Python-based web framework for perfectionists with deadlines that’s ridiculously fast, reassuringly secure, and exceedingly scalable. But if you don’t know much about it, this article will serve as an extremely fast hands-on introduction.

About Vue.js

Same deal with Vue.js, the progressive JavaScript framework that’s approachable, versatile, and performant. If you aren’t familiar with it, here you’ll get a quick-and-dirty introduction.

I’ll also cover two official Vue.js libraries:

Python + JavaScript = WIN!

For this article, we’ll set a publishing project with a basic database schema to save authors and articles, and a minimal user interface (UI) to interact with them.

Hopefully, this will serve as a toy program to understand how to integrate Django using Python code on the back end with a JavaScript framework on the front end, that you can adapt later to your own needs.

1. Setting Up a Django Project

Very quickly, we’ll set up a project from scratch. If you already know how to handle Django projects, you can skip this section. We’ll assume you already have Python installed.

For a more in-depth guide, see How to install Django on the Django documentation site.

Python Virtual Environment

Let’s open a console and create a virtual environment (see Virtual Environments and Packages for more info):

$ virtualenv myenvironment

Using base prefix 'c:\users\luzdealba\appdata\local\programs\python\python37'
New python executable in C:UsersluzdealbaDevelopmentmyenvironmentScriptspython.exe
Installing setuptools, pip, wheel …
done

Don’t mind the paths, as these will change from system to system.

Let’s access and activate the virtual environment.

On Windows:

$ cd myenvironment
$ Scriptsactivate

On macOS and Linux:

$ cd myenvironment
$ source bin/activate

Django Package

Let’s install Django:

(myenvironment) $ pip install django

Collecting django Downloading Django-3.0.3-py3-none-any.whl (7.5 MB)
Collecting sqlparse>=0.2.2 Downloading sqlparse-0.3.1-py2.py3-none-any.whl (40 kB)
Collecting pytz Using cached pytz-2019.3-py2.py3-none-any.whl (509 kB)
Collecting asgiref~=3.2 Downloading asgiref-3.2.3-py2.py3-none-any.whl (18 kB)
Installing collected packages: sqlparse, pytz, asgiref, django
Successfully installed asgiref-3.2.3 django-3.0.3 pytz-2019.3 sqlparse-0.3.1

One more time, don’t mind about program versions and file sizes as these will vary.

The Project

Let’s start a project called myproject:

(myenvironment) $ django-admin startproject myproject

Let’s access the project:

(myenvironment) $ cd myproject

The App

Start an app called myapp:

(myenvironment) $ django-admin startapp myapp

And add myapp.apps.MyappConfig to the INSTALLED_APPS constant list in myproject/settings.py to enable the app.

2. Setting Up the Database with Django

Here we’ll define the back-end database with Django, which we’ll later integrate with a front-end storage with Vuex.

Django Models

Models are Django’s way to implement an object-relational database management system (ORDBMS). In other words, plain text files where you can define database tables and fields, and from where these will propagate to the application layer and the DB engine.

Let’s code the following models for our app in myapp/models.py:

from django.db import models class Article(models.Model): """Table schema to store articles.""" name = models.CharField(max_length=64) author = models.ForeignKey('myapp.Author', on_delete=models.CASCADE) content = models.TextField() slug = models.CharField(default='', max_length=64) def __str__(self): return '%s' % self.name class Author(models.Model): """Table schema to store auhtors.""" name = models.CharField(max_length=64) slug = models.CharField(default='', max_length=64) def __str__(self): return '%s' % self.name

Notice that we implemented a URL slug for both articles and authors.

For more information, see Model API reference on the Django documentation site.

Django Admin

Before we can manage these models through the admin site, we’ll first need to register them so that Django makes them available to us.

Let’s simply edit myapp/admin.py so that it looks like this:

from django.contrib import admin from .models import Article
from .models import Author # register models to use in admin site
admin.site.register(Article)
admin.site.register(Author)

Read more about the Django admin site on the Django documentation site.

Django Migrations

From the Django’s Migrations documentation:

Migrations are Django’s way of propagating changes you make to your models (adding a field, deleting a model, etc.) into your database schema.

In short: migrations do everything; no SQL commands required.

First, let’s create the migration files:

(myenvironment) $ python manage.py makemigrations

Migrations for 'myapp': myappmigrations001_initial.py - Create model Author - Create model Article

Now let’s use that information to update the database:

(myenvironment) $ python manage.py migrate

Operations to perform: Apply all migrations: admin, auth, contenttypes, myapp, sessions
Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying myapp.0001_initial... OK Applying sessions.0001_initial... OK

Don’t mind the long list. That’s because that was our first migration, so not only our Author and Article tables were created, but also all of Django’s default schemas.

For more info, see Migration operations in the Django documentation site.

3. A Basic Interface with Vue Components in a Django Template

Here’s one of the most interesting parts of the mashup, as we’ll mix closely related tools from different technologies.

Django View

Django goes by the model–view–controller (MVC) software design pattern, which divides the related program logic into three interconnected elements.

We’ll code the following view in myapp/views.py:

from django.shortcuts import render from .models import Article
from .models import Author def frontend(request): """Vue.js will take care of everything else.""" articles = Article.objects.all() authors = Author.objects.all() data = { 'articles': articles, 'authors': authors, } return render(request, 'myapp/template.html', data)

Notice that we queried all of the articles and authors form the database. That will come in handy later.

See more about writing views and class-based views (API) in the Django documentation site.

Django Template

Django has a rich template language with built-in template tags and filters, and an API for Python programmers; but yes, you guessed it — we won’t cover much of that here. 😅

What we will do, however, is use Bootstrap’s Starter template to set a very basic navigation layout for the app:

  • light gray background
  • white foreground
  • centered content

So we’ll code the following template in myapp/templates/myapp/template.html (you’ll need to create the sub-directories templates/myapp/ within myapp/):

<!doctype html>
<html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> <title>Django and Vue.js</title> </head> <body class="bg-light"> <div class="bg-white container"> <h1>Prototyping a Web App with Django and Vue.js</h1> <!-- Content --> </div> <!-- Vue.js --> <script src="https://unpkg.com/vue"></script> <script src="https://unpkg.com/vue-router"></script> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> </body>
</html>

Read more about Django templates.

Vue.js Templates

Vue.js also has a template syntax available, and it furthermore allows us to create our own HTML tags.

We’ll create four templates: article-list, author-list, article-item and author-item:

<template id="article-list-template"> <div class="article-list"> <h2>Articles</h2> <article-item v-for="article in articles" v-bind:key="article.slug" v-bind:name="article.name" v-bind:slug="article.slug" v-bind:content="article.content" ></article-item> </div>
</template> <template id="author-list-template"> <div class="author-list"> <h2>Authors</h2> <author-item v-for="author in authors" v-bind:key="author.slug" v-bind:name="author.name" v-bind:slug="author.slug" ></author-item> </div>
</template> <template id="article-item-template"> <div class="article-item"> <span v-if="$route.params.slug"> <h3> <router-link v-bind:to="'/article/' + $route.params.slug + '/'" v-html="$store.getters.getArticleBySlug($route.params.slug)['name']" ></router-link> </h3> <div v-html="$store.getters.getArticleBySlug($route.params.slug)['content']"></div> </span> <span v-else> <h3> <router-link v-bind:to="'/article/' + slug + '/'" v-html="name" ></router-link> </h3> <div v-html="content"></div> <hr /> </span> </div>
</template> <template id="author-item-template"> <div class="author-item"> <span v-if="$route.params.slug"> <b> <router-link v-bind:to="'/author/' + $route.params.slug + '/'"> [[ $store.getters.getAuthorBySlug($route.params.slug)['name'] ]] </router-link> </b> ([[ $route.params.slug ]]) </span> <span v-else> <b> <router-link v-bind:to="'/author/' + slug + '/'"> [[ name ]] </router-link> </b> ([[ slug ]]) </span> </div>
</template>

Breakdown

What we did here, in a nutshell:

  1. List rendering with v-for.
  2. Data binding in HTML attributes with v-bind.
  3. Conditional rendering with v-if and v-else.
  4. Raw HTML rendering with v-html.

$store and $route will make more sense in a moment when we introduce storage and routing.

Text Interpolation between Django and Vue.js Templates

Just like with Django, the most basic form of text interpolation in Vue.js is with the “Mustache” syntax (double curly brackets).

Like this:

<span>Message: {{ msg }}</span>

In order to avoid conflicts between the two, when instantiating Vue.js we’ll set it to use double square brackets instead:

delimiters: ['[[', ']]']

Vue.js Themes

Did you know you can “theme” Vue.js?

Vue.js Components

Components are reusable Vue instances. What that means is that we can define a component, code an HTML template for it, and then use it as many times as we need it with Vue.js handling the DOM for us.

We’ll show the entire single-page application (SPA) code in the end. For now, let’s introduce a few snippets.

Just as with templates, we’ll define four components — ArticleList, AuthorList,, ArticleItem, and AuthorItem:

ArticleList = Vue.component('article-list', { data: function () { return { articles: store.state.articles } }, template: '#article-list-template',
}); AuthorList = Vue.component('author-list', { data: function () { return { authors: store.state.authors } }, template: '#author-list-template',
}); ArticleItem = Vue.component('article-item', { delimiters: ['[[', ']]'], props: ['name', 'slug', 'content'], template: '#article-item-template',
}); AuthorItem = Vue.component('author-item', { delimiters: ['[[', ']]'], props: ['name', 'slug'], template: '#author-item-template',
});

Breakdown

  1. When in a component, data must be a function ($store will be explained in a moment).
  2. We use the templates previously defined.
  3. To disambiguate text interpolation, make sure you set delimiters that are different from Django’s ({{/}}).
  4. We use props listed as an array to pass data to our components.

4. Connecting Vue.js Store to Collect Django’s Database Using Vuex

A rich front end might have many loosely coupled components, each with its own set of parameters, which can make it complicated to share data or to manage the state of variables.

Here’s where Vuex comes in handy:

It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.

Creating a Vuex Store

Let’s create and define a store in our Django template, and let’s use the data shared in the Django View:

const store = new Vuex.Store({ state: { authors: [ {% for author in authors %} { name: '{{ author.name }}', slug: '{{ author.slug }}', }, {% endfor %} ], articles: [ {% for article in articles %} { content: '{{ article.content | linebreaksbr }}', name: '{{ article.name }}', slug: '{{ article.slug }}', }, {% endfor %} ], }, getters: { getArticleBySlug: (state) => (slug) => { return state.articles.find(articles => articles.slug === slug) }, getAuthorBySlug: (state) => (slug) => { return state.authors.find(authors => authors.slug === slug) }, }
})

Breakdown

Let’s review what just happened:

  1. We’ve created a store using Vuex.Store().
  2. We defined a Vuex state where all of the articles and authors are collected.
  3. We used the for loop that’s built into the Django templates to iterate through all of the articles and authors.
  4. We’ve created two Vuex getters to get an article or an author by their slug, getArticleBySlug and getAuthorBySlug, respectively.

There’s a lot more to Vuex, so make sure to check the Getting Started guide and the API reference.

5. Routing URLs between Django and Vue Router

Django has a powerful URL dispatcher, which we’ll use in combination with Vue.js routing.

We’ll create an application that:

  1. uses dynamic route matching to seamlessly switch between pages without refreshes (see example)
  2. works with nested routes (see example)

With Django

Reactive URLs usually need a special configuration on the server to work properly, but Django lets us design URLs however we want, so no need to set rewrite rules for Apache or NGINX.

We’ll edit myproject/urls.py so that it uses the base directory / as the route for our app:

from django.contrib import admin
from django.urls import path # don't forget to import the app's view!
from myapp import views as myapp_views urlpatterns = [ path('admin/', admin.site.urls), # paths for our app path('', myapp_views.frontend), path('article/<slug:slug>/', myapp_views.frontend), path('author/<slug:slug>/', myapp_views.frontend),
]

With Vue Router

By default, Vue Router uses “hash mode” (i.e: http://site/#/path) as a JavaScript trick to load parts of the page using anchors. However, we’ll leverage Vue Router’s HTML5 History Mode, which means that all of our URLs will change seamlessly without reloading the page and without using hashes.

We’ll set the router to match each path to their respective component previously defined:

const routes = [ { component: ArticleList, path: '/article/', }, { component: AuthorList, path: '/author/', }, { component: ArticleItem, path: '/article/:slug/', }, { component: AuthorItem, path: '/author/:slug/', },
] const router = new VueRouter({ mode: 'history', routes: routes,
})

As we can see, the syntax to define paths is slightly different from Django’s, but it’s essentially the same thing.

Read more about the Vue Router.

6. Testing Everything

Now that we’ve got all of the pieces together, it’s time to do some gray box testing and see how things work!

Create a Django Superuser

Before we can log in to the admin, we’ll need to create a superuser.

Let’s create an administrator:

(myenvironment) $ python manage.py createsuperuser

Next, you’ll enter the username, email address, and password (twice).

Run a Local Server

We’ll run Django’s built-in server with runserver to launch the website on our local system.

On a console:

(myenvironment) $ python manage.py runserver

Watching for file changes with StatReloader
Performing system checks... System check identified no issues (0 silenced).
March 09, 2020 - 19:41:22
Django version 3.0.3, using settings 'myproject.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.

Create Database Entries

We’ll now populate the database so that we can browse something on the front end.

Let’s head to http://127.0.0.1:8000/admin/ and enter the admin credentials you’ve just created so that we can create two authors and four articles:

  1. On the MYAPP pane, next to Authors, click on the Add link and create at least two authors.Two authors added
  2. On the MYAPP pane, next to Articles, click on the Add link and create at least two different articles for each author.Articles added for each author

Notice that you must add articles after having created a few authors so that you can link them.

Browse the Site!

Now’s the time to see how it all plays together!

Full SPA Code

You can navigate all of the project code in my GitHub repository, luzdealba / djangovuejs.

Anyway, this is probably what you’re most interested in:

<!doctype html>
<html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- Bootstrap CSS --> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> <title>Django and Vue.js</title> <style> .router-link-active { color: black; text-decoration: none; } </style> </head> <body class="bg-light"> <div class="bg-white container"> <div class="jumbotron"> <h1 class="display-4">Django and Vue.js</h1> <p class="lead"> Wouldn’t it be cool if you could prototype a custom web application that’s responsive (mobile ready), reactive (light-speed fast), with a full–featured back office site to manage the content; all of that in no time? Actually, with a mashup between Django’s and Vue.js, you can! 😁 </p> </div> <!-- Content --> <div id="myapp"> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <ul class="navbar-nav mr-auto"> <li class="nav-item"> <router-link class="nav-link text-primary" to="/author/" > Go to Authors </router-link> </li> <li class="nav-item"> <router-link class="nav-link text-primary" to="/article/" > Go to Articles </router-link> </li> </ul> </nav> <br /> <router-view></router-view> </div> </div> <!-- Vue.js --> <script src="https://unpkg.com/vue"></script> <script src="https://unpkg.com/vue-router"></script> <script src="https://unpkg.com/vuex"></script> <!-- Vue templates --> <template id="article-list-template"> <div class="article-list"> <h2>Articles</h2> <article-item v-for="article in articles" v-bind:key="article.slug" v-bind:name="article.name" v-bind:slug="article.slug" v-bind:content="article.content" ></article-item> </div> </template> <template id="author-list-template"> <div class="author-list"> <h2>Authors</h2> <author-item v-for="author in authors" v-bind:key="author.slug" v-bind:name="author.name" v-bind:slug="author.slug" ></author-item> </div> </template> <template id="article-item-template"> <div class="article-item"> <span v-if="$route.params.slug"> <h3> <router-link v-bind:to="'/article/' + $route.params.slug + '/'" v-html="$store.getters.getArticleBySlug($route.params.slug)['name']" ></router-link> </h3> <div v-html="$store.getters.getArticleBySlug($route.params.slug)['content']"></div> </span> <span v-else> <h3> <router-link v-bind:to="'/article/' + slug + '/'" v-html="name" ></router-link> </h3> <div v-html="content"></div> <hr /> </span> </div> </template> <template id="author-item-template"> <div class="author-item"> <span v-if="$route.params.slug"> <b> <router-link v-bind:to="'/author/' + $route.params.slug + '/'"> [[ $store.getters.getAuthorBySlug($route.params.slug)['name'] ]] </router-link> </b> ([[ $route.params.slug ]]) </span> <span v-else> <b> <router-link v-bind:to="'/author/' + slug + '/'"> [[ name ]] </router-link> </b> ([[ slug ]]) </span> </div> </template> <!-- Vue app --> <script> // store const store = new Vuex.Store({ state: { authors: [ {% for author in authors %} { name: '{{ author.name }}', slug: '{{ author.slug }}', }, {% endfor %} ], articles: [ {% for article in articles %} { content: '{{ article.content | linebreaksbr }}', name: '{{ article.name }}', slug: '{{ article.slug }}', }, {% endfor %} ], }, getters: { getArticleBySlug: (state) => (slug) => { return state.articles.find(articles => articles.slug === slug) }, getAuthorBySlug: (state) => (slug) => { return state.authors.find(authors => authors.slug === slug) }, } }) // components ArticleList = Vue.component('article-list', { data: function () { return { articles: store.state.articles } }, template: '#article-list-template', }); AuthorList = Vue.component('author-list', { data: function () { return { authors: store.state.authors } }, template: '#author-list-template', }); ArticleItem = Vue.component('article-item', { delimiters: ['[[', ']]'], props: ['name', 'slug', 'content'], template: '#article-item-template', }); AuthorItem = Vue.component('author-item', { delimiters: ['[[', ']]'], props: ['name', 'slug'], template: '#author-item-template', }); // router const routes = [ { component: ArticleList, path: '/article/', }, { component: AuthorList, path: '/author/', }, { component: ArticleItem, path: '/article/:slug/', }, { component: AuthorItem, path: '/author/:slug/', }, ] const router = new VueRouter({ mode: 'history', routes: routes, }) // app const myapp = new Vue({ router, store, }).$mount('#myapp'); </script> <!-- jQuery first, then Popper.js, then Bootstrap JS --> <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> </body>
</html>

Looking Forward: Microservices!

At this point, you already have a solid prototype that can work as a proof of concept to present an idea to your prospect clients or colleagues, or as a foundation for your own project.

While the interface we created can present database registries, you can’t really interact with them in any other way through the front end, such as making new additions, editions, or deleting such entries. For that, you’ll need an API.

Believe it or not, implementing a microservice through a REST API is fairly straightforward with Django. All you need is the Django REST framework add-on, which is extremely well documented and, as all things Django, powerful, flexible, and secure.

With an exposed API, the next thing you can do is manage data right there on your front end with Vue.js. I can’t cover the details here, but you can check the “Using Axios to Consume APIs” article in the Vue.js Cookbook.

Wrapping Up

How’s that for a primer on full-stack development? We’ve prototyped a project that can be the foundation for a web application.

And I didn’t water down anything! In fact, because we’re using Vuex storage for state management and Vue Router for dynamic route matching from the get-go, there aren’t substantial changes that we’ll need to do as the application scales. So you can essentially take it from there and expand in whichever direction you need to — customizing the database, improving the interface, and even creating a microservice!

Don’t be shy if your Python or JavaScript knowledge is somewhat limited. We all need to start somewhere. Read further, code further, and stay curious!

Source: https://www.sitepoint.com/web-app-prototype-django-vue/?utm_source=rss

spot_img

Latest Intelligence

spot_img

Chat with us

Hi there! How can I help you?