Django Framework

VueJS e Django Framework #3: Como usar os dois juntos

12 de junho de 2017
VueJS e Django Framework: Como usar - parte #3

Chegamos na parte final da nossa série de posts sobre como usar o VueJS e Django Framework juntos num mesmo projeto. Nesta parte 3 nós veremos como incluir o VueJS no projeto e criar um componente para carregar os dados do model Games de forma totalmente independente. Isto significa dizer que as informações não serão mais renderizadas diretamente pelo Django, mas por meio de um componente que fará uma requisição via Ajax para uma view do Django.

VueJS e Django Framework parte #1: Como usar os dois juntos

VueJS e Django Framework parte #2: Como usar os dois juntos

Criando as pastas necessárias

Dentro da pasta static/ crie a seguinte estrutura de pastas:

static
  -> js
       -> components
  -> public

Criamos uma pasta chamada static/js, e dentro desta nós criamos a pasta components/. É aí onde vamos criar o nosso componente .vue. A pasta static/public/ vai conter o arquivo JS final, já empacotado, como você verá a seguir.

Instalando os pacotes do NodeJS via comando NPM

1 – Crie um arquivo chamado packages.json na raiz do projeto e inclua nele o seguinte conteúdo:

{
  "name": "django-vuejs",
  "version": "1.0.0",
  "description": "",
  "main": "App.js",
  "author": "",
  "license": "ISC",
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
  },
  "dependencies": {
    "lodash": "^4.17.4",
    "vue": "^2.3.3",
    "vue-resource": "^1.3.4"
  },
  "devDependencies": {
    "babel-core": "^6.0.0",
    "babel-loader": "^6.0.0",
    "babel-preset-env": "^1.5.1",
    "cross-env": "^3.0.0",
    "css-loader": "^0.25.0",
    "file-loader": "^0.9.0",
    "vue-loader": "^12.1.0",
    "vue-template-compiler": "^2.3.3",
    "webpack": "^2.6.1"
  }
}

Neste vídeo eu explico um pouco mais sobre o conteúdo deste arquivo.

Feito isto, nós precisamos agora instalar os pacotes que estão presentes no JSON que você acabou de incluir no arquivo packages.json. Para isto, rode o seguinte comando:

npm install

Configurando o WebPack

No arquivo packages.json existe uma linha informando o webpack como uma dependência do nosso projeto com o Vue. Se você não sabe do que se trata o webpack, sugiro que veja neste link. Em suma, o webpack é um empacotador (bundler), que permite dividir o nosso código JS em múltiplos módulos para serem lidos sob demanda. Nós vamos utilizar o webpack em nosso projeto. Ele foi instalado quando rodamos o comando $ npm install, agora nós precisamos configurá-lo para que ele trabalhe corretamente a nosso favor. Para isto, crie o arquivo wepack.config.js, na raíz do projeto, e inclua o seguinte conteúdo:

var path = require('path')
var webpack = require('webpack')

module.exports = {
  entry: './static/js/App.js',
  output: {
    path: path.resolve(__dirname, './static/public'),
    publicPath: '/static/public/',
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
          }
          // other vue-loader options go here
        }
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]?[hash]'
        }
      }
    ]
  },
  performance: {
    hints: false
  },
  devtool: '#eval-source-map'
}

if (process.env.NODE_ENV === 'production')
{
  module.exports.devtool = '#source-map'

  module.exports.plugins = (module.exports.plugins || []).concat([
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,
      compress: {
        warnings: false
      }
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true
    })
  ])
}

Neste vídeo (que acompanha o primeiro post desta série) eu explico um pouco sobre estas configurações presentes no webpack.config.js. Resumidamente, você precisa saber que isto vai permitir que o webpack leia todo o código, presente ou importado, no arquivo de entrada /static/js/App.js e gere um arquivo de saída static/public/bundle.js. O arquivo bundle.js vai conter todo o código transpilado a partir da syntaxe ES6 para a ES5, que é compatível com todos os browsers hoje. Para transpilar este código, o webpack vai fazer uso do babel-loader e e do vue-loaders (para o caso dos componentes *.vue). No final, o webpack faz uso do UglifyJsPlugin para otimizar este código em produção, minificando o código do bundle.js

Criando o arquivo componente Exemplo.vue

Dentro da pasta static/js/components, nós vamos criar um arquivo chamado Exemplo.vue. Dentro deste arquivo, inclua o seguinte conteúdo:

<template>
  <div id="exemplo">

    <table class="table">
      <thead>
        <th><a href="#" @click="sort($event, 'fields.titulo')">Titulo</a></th>
        <th><a href="#" @click="sort($event, 'fields.plataformas')">Plataformas</a></th>
      </thead>
      <tbody>
        <tr v-for="game in lista">
          <td>{{ game.fields.titulo }}</td>
          <td>{{ game.fields.plataformas }}</td>
        </tr>
      </tbody>
    </table>

  </div>
</template>

<script>
export default {

  data() {
    return {
      sortDirection: 'desc',
      lista: []
    }
  },
  mounted() {
    this.$http.get("/homepage/games").then( (req) => this.lista = req.data )
  },
  methods:{
    sort(event, campo){
      event.preventDefault()

      if ( this.sortDirection == "desc" )
      {
          this.sortDirection = "asc"
      }else{
        this.sortDirection = "desc"
      }

      this.lista = _.orderBy(this.lista, campo, this.sortDirection)

    }
  }
}
</script>

<style>

  #exemplo{
      color: red;
  }
</style>

Nosso arquivo componente possui três blocos. O primeiro é a parte do HTML, que fica entre as tags <template></template> e que o lugar onde o componente renderiza o conteúdo JSON, obtido via AJAX,  a partir do bloco <script>. O bloco <script>, por sua vez, monta o nosso componente e obtém os dados que salvamos no model Games, na parte #2 desta série. O bloco <style> é usado para estilizar o que estiver contido dentro do primeiro bloco.

Sobre obter o conteúdo da view Django, via Ajax, você deve ter notado que isto é feito precisamente dentro deste método:

mounted() {
  this.$http.get("/homepage/games").then( (req) => this.lista = req.data )
}

Ou seja, logo que o componente é montado na tela, a requisição AJAX é realizada, utilizando o módulo vue-resource. Logo depois que os dados são retornados, ele serão atribuídos ao array lista. No momento em que este array for populado, o VueJS automaticamente rederiza o conteúdo no HTML, baseado no seguinte loop:

<tr v-for="game in lista">
     <td>{{ game.fields.titulo }}</td>
     <td>{{ game.fields.plataformas }}</td>
</tr>

Acontece que nós ainda não criamos o código que é chamado no momento em que a url /homepage/games é acessada via Ajax. Este código precisa ser criado em homepage/views.py. Já vamos criá-lo :-).

Nós temos ainda um método que vai nos permitir ordenar os registros em ordem crescente, ou decrescente. Este método é o sort() e ele é chamado por meio de um evento disparado quando qualquer um dos seguintes links for clicado:

<th><a href="#" @click="sort($event, 'fields.titulo')">Titulo</a></th>
<th><a href="#" @click="sort($event, 'fields.plataformas')">Plataformas</a></th>

Note que, no segundo parâmetro do evento nós passamos o nome do campo que queremos usar para ordenar os dados. Esta ordenação é feita por meio do método _.orderBy(), do módulo Lodash.

 

Criando o código que vai retornar o JSON via Ajax

Nós precisamos modificar o nosso arquivo homepage/views.py, de modo a obter os dados via model Games, criado no arquivo anterior, em seguida serializar estes dados e retornar um JSON. O código vai ficar assim:

from django.shortcuts import render
# acrescentamos estas duas linhas abaixo
from django.core.serializers import serialize
from django.http import HttpResponse

from .models import Games

def index(request):
    games = Games.objects.all()
    return render(request, "homepage/index.html", { 'games': games })

# acrescentamos este código abaixo
def games(request):
    games = serialize("json", Games.objects.all())
    return HttpResponse(games, content_type="application/json")

A função games() acima é a responsável por esta etapa do processo.

Precisamos agora incluir uma entrada no arquivo homepage/urls.py, de modo que a função acima possa ser chamada ao acessar a url /homepage/games, via AJAX. Fica assim:

from django.conf.urls import url

from . import views

urlpatterns = [
    url(r'^$', views.index, name="index"),
    url(r'^homepage/games$', views.games, name="games"), # acrescentamos esta linha
]

Agora falta criarmos o arquivo de entrada App.js, que vai montar o nosso componente dentro de uma <div/> que vamos ainda criar no arquivo homepage/templates/homepage/index.html. Já chegaremos nesta parte.

Criando o arquivo de entrada static/js/App.js e montando o componente

este arquivo vai importar os módulos do NodeJS que estamos utilizando diretamente, bem como o componente que acabamos de criar. Também vai conter a linha que vai criar uma instância do Vue e montar o nosso componente Exemplo.vue dentro de um <div>. Crie o arquivo static/js/App.js e inclua o seguinte código dentro dele:

import Vue from 'vue'
import VueResource from 'vue-resource'
import Exemplo from './components/Exemplo.vue'
import lodash from 'lodash'

Vue.use(VueResource, lodash)

new Vue(Exemplo).$mount(".exemplo")

Agora precisamos modificar o arquivo homepage/templates/homepage/index.html para que fique assim:

{% extends "layout.html" %}
{% block content %}

<div class="exemplo"></div>

{% endblock %}

Nosso componente será montado exatamente nesta linha, dentro da div: <div class=”exemplo”></div>.

Criando o arquivo .babelrc

nós iremos criar o arquivos .babelrc, na pasta raíz do nosso projeto. Dentro deste arquivo nós podemos setar algumas flags utilizadas pelo babel no momento de transpilar o nosso código JS. Sempre que o babel for executado, ele irá verificar a presença deste arquivo dentro do nosso projeto e respeitar o conteúdo presente dentro dele. Insira o seguinte conteúdo dentro do arquivo .babelrc criado:

{
  "presets": [
    ["env", {"modules": false}]
  ]
}

Saiba mais sobre o .babelrc aqui.

Gerando o arquivo static/public/bundle.js

Para gerar o arquivo bundle.js, digite o seguinte comando, a partir da raíz do projeto Django:

webpack

Depois de alguns segundos, o webpack vai gerar o arquivo já transpilado. Caso queira gerar o arquivo também minificado, será preciso setar o ambiente para “produção”, da seguinte forma:

export NODE_ENV=production

em seguida rodar o comando $ webpack novamente.

Chamando o arquivo bundle.js em templates/layout.html

Agora, nós precisamos fazer com que o arquivo bundle.js seja chamado no navegador. Para isto, temos que incluir a seguinte chamada, antes da tag </body>, no arquivo templates/layouts.html:

<script src="{% static 'public/bundle.js' %}"></script>

 

Agora, quando você acessar o endereço http://127.0.0.1:8000, deverá ver a seguinte tela:

 

Django com VueJS

E com isso terminamos a nossa pequena série de posts sobre como integrar o VueJS e o Django. Apesar de os posts terem ficados demasiado longos, a integração entre estas duas ferramentas é bem mais simples do que parece. Espero que você não tenha tido dificuldade em seguir todos os passos abordados aqui. De qualquer forma, sinta-se a vontade para deixar suas dúvidas no espaço de comentários que fica logo abaixo deste post :-).

Like

Veja também

  • Giulliano Ferreira

    É possível realizar esses mesmos passos usando a ferramenta de comando do Vue pra cirar o projeto Vue automaticamente na pasta static?

    • Giulliano, como vai? Você pode gerar a estrutura do projeto VueJS utilizando o utilitário de comando da ferramenta, configurando os parâmetros de modo a gerar esta estrutura na pasta static, ou em qualquer outra que você quiser, desde que o Django possa utilizar a mesma pasta para ler os arquivos estáticos. No Django, você é livre para escolher qual será esta pasta (Ver STATICFILES_DIRS, no seu arquivo settings.py).