【Django】DjangoによるWeb開発プロジェクトPart5 - 本格的なフロントエンドをVueで書いていく -

## はじめに

年内リリースに向けての課題はまだまだありますが、リリースに向けての登竜門はそろそろ目処が経ちそうなので、開発と執筆のペースを上げます。

## フロントエンドをVue.jsで開発していく

[【Django】DjangoによるWeb開発プロジェクトPart3 - webpackでフロントエンドをDjangoから独立させるまで -](https://qiita.com/himrock922/items/facef344ad20f307720e)の時に、フロントエンドをVue.jsで開発していく事を決めました。いよいよ、本格的にフロントエンドも開発していこうと思います。
まずは、おさらいとしてwebpackで単一コンポーネントのVue.jsを書ける所までを設定していきます。

### サンプル例

今回は、オブジェクトを生成するフォーム、及びaxios経由でDjangoのviews.pyのメソッドを叩ける所まで行います。

### サンプルコードがDjangoのTemplateでも動くか検証

```python:templates/***/new.html
{% extends 'application.html' %}

{% block title %}**** New{% endblock %}

{% block content %}
  <main class="dashboard">
    <nav class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
      <a href="{% url '***:index' %}" class="navbar-brand col-sm-3 col-md-2 mr-0">Jails</a>
      <ul class="navbar-nav px-3">
        <li class="nav-item text-nowrap">
          <a class="nav-link" href="{% url 'logout' %}">log out</a>
        </li>
      </ul>
    </nav>
    <div class="container-fluid">
      <h2>*** New</h2>
      {% verbatim %}
        <div id="app">
          {{ message }}
        </div>
      {% endverbatim %}
    </div>
  </main>
{% endblock %}
```

Djangoで書く場合に注意することが一つあります。それは、`Vue.jsの変数展開の記法とtemplateの記法が被っている事です({{ }})。`
そのため、テンプレートHTMLファイルの中でVue.jsのコードを書きたい場合はtemplateエンジンのレンダリングを一時的にストップしなければなりません。

`{% verbatim %}`を利用する事で、テンプレートエンジンのレンダリングを一時的にストップする事ができます。

また、Vueにはランタイム限定ビルドと完全ビルドの2種類があります。
今回のアプリケーションはほぼバックエンドで動かすようなアプリケーションなため、完全ビルドの方法を採用しました。

```js:webpack.config.js
// webpack.config.js
var path = require('path')
var BundleTracker = require('webpack-bundle-tracker')

module.exports = {
    entry: './frontend/packs/application.js',
    output: {
        path: path.resolve(__dirname, './public/bundles/'),
        filename: "[name]-[hash].js",
    },
    plugins: [
        new BundleTracker({filename: './webpack-stats.json'})
    ],
    module: {
        rules: [
            {
                test: /\.js$/,
                include: [ // use `include` vs `exclude` to white-list vs black-list
                    path.resolve(__dirname, "node_modules"), // white-list your app source files
                    require.resolve("bootstrap-vue"), // white-list bootstrap-vue
                ],
                loader: "babel-loader"
            },
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            }
        ]
    },
    resolve: {
        alias: {
            'vue$': 'vue/dist/vue.esm.js'
        }
    }
}
```

`alias`でvueを設定しています。

最後にテスト用に`application.js`に以下のサンプルコードを書いてみます。

```js:application.js
document.addEventListener("DOMContentLoaded", function (event) {
    var app = new Vue({
        el: '#app',
        data: {
            message: 'Hello Vue!'
        }
    })
});
```

DOMが完全にコンテンツを読み込んでから、Vueを実行したいために、`document.addEventListener("DOMContentLoaded", function (event)`をかましています。

```bash
./node_modules/.bin/webpack --config webpack.config.js --mode=development
```

以上で、Vueのサンプルは動いたので、ここから本格的に開発をしていきます。

## 単一ファイルコンポーネントによるVueインスタンスの登録

Vueをより良く使うために、コンポーネントを利用します。コンポーネントは名前付きの再利用可能なVueインスタンスです。ルートVueインスタンス内でよりカスタマイズした要素として利用できるようになります。また、カスタマイズしたコンポーネントは、Webpack や Browserify のビルドツールにより実現された `.vue`拡張子のファイルで書くと、CSSやHTML、Vueの記述を一つにまとめる事ができます(単一ファイルコンポーネント)
以下にサンプルを書いてみます。

```vue:***.vue
<template>
  <div><p v-if="isVisible">{{ message }}</p></div>
</template>

<script>
    module.exports = {
        name: '****',
        data: function () {
            return {
                message: 'hello'
            }
        },
        computed: {
            isVisible: function () {
                if (this.message) {
                    return true;
                } else {
                    return false;
                }
            }
        }
    }
</script>

<style scoped>

</style>
```

内容自体は単純に`hello`という文字列を返しているだけです。
さらに、これを`application.js`側でRootVueインスタンスを生成し、コンポーネント登録します。

```js:application.js
import Example from './****.vue'

document.addEventListener("DOMContentLoaded", function (event) {
    new Vue({
        el: '#app',
        components: {
            'my-component': Example
        }
    })
})
```

これにより、Djangoのtemplateファイルでは`<my-component>`を記述する事で、RootVueインスタンスを呼び出す事ができます。

```html:templates/jails/new.html
{% extends 'application.html' %}

{% block title %}**** New{% endblock %}

{% block content %}
  <main class="dashboard">
    <div class="container-fluid">
      <h2>**** New</h2>
      <div id="app">
        <my-component></my-component>
      </div>
    </div>
  </main>
{% endblock %}
```

![スクリーンショット 2018-12-30 03.01.54.png](https://qiita-image-store.s3.amazonaws.com/0/144991/390f8b6b-2f02-09cb-23c2-c2acb1ddef79.png)

本当はそろそろ、Vuexとか使うべきかとは思われますが、まだフロントエンド開発に慣れていないため、このように単一ファイルコンポーネントを作成し、RootVueインスタンスで呼び出せるように作っていきます。

## 単一ファイルコンポーネントでPUTリクエストを書いていく

今回は、VMの起動、停止のようなボタンをAjax経由で操作できるようなPUTリクエストをVue.jsで書いていきます。Vue.jsでは、Ajaxの代わりに、`axios`というHTTPクライアントを利用して、リモートDjangoのメソッドを叩けるようにします。

```js:application.js
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue'

Vue.use(BootstrapVue);

import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import '../css/application.css'
import '../css/login.css'
import '../css/top.css'
import VMStop from './vm_stop.vue'
import VMStart from './vm_start.vue'

document.addEventListener("DOMContentLoaded", function (event) {
    new Vue({
        el: '#app',
        components: {
            'vm-stop': VMStop,
            'vm-start': VMStart,
        }
    })
})
```

`my-component`の時と同じように、VM起動、停止のコンポーネントを作っていきます。

```vue:vm_start.vue
<template>
  <button type="button" class="btn btn-success" @click="vm_start">Start</button>
</template>

<script>
    import axios from 'axios'

    axios.defaults.xsrfCookieName = 'csrftoken'
    axios.defaults.xsrfHeaderName = 'X-CSRFToken'
    export default {
        name: 'VM-Start',
        props: {
            vm_name: String
        },
        methods: {
            vm_start: function () {
                axios.put('http://*****:8080/jails/start', {
                    vm_name: this.vm_name
                }).then(response => (console.log(response)))
            }
        }
    }
</script>

<style scoped>

</style>
```

CSRFトークンをaxios経由でハンドリングできるようにするために、

```js

    axios.defaults.xsrfCookieName = 'csrftoken'
    axios.defaults.xsrfHeaderName = 'X-CSRFToken'
```

で、axiosのヘッダにCSRFトークンをアタッチします。

また、プロパティを定義する事で、親コンポーネントで記述されたデータの値を子ポーネントに渡すことができます。

```js
        props: {
            vm_name: String
        },

        vm_name: this.vm_name
```

これにより、Djangoのtemplateファイルの方で直接データを渡す事ができます。

```html:index.html
  <jail-start vm_name={{ vm.name }}></jail-start>
```

最後にDjangoのurls.pyでPUTリクエストに対応するパスを生成し、メソッドの中身を記述します。

```python:urls.py
    path('start', views.start, name='start'),
    path('stop', views.stop, name='stop'),
```

```python:views.py

from django.shortcuts import render
from django.http import Http404
from django.http import HttpResponse
import json

def start(request):
    try:
        response = json.loads(request.body)
        *** = *****(response['jail_name'])
        ***.start()
    except (Exception, SystemExit):
        raise Http404("**** does not exist")
    return HttpResponse('OK')

def stop(request):
    try:
        response = json.loads(request.body)
        *** = ****(response['jail_name'])
        ***.stop()
    except (Exception, SystemExit):
        raise Http404("**** does not exist")
    return HttpResponse('OK')
```

以上で、VMの起動・停止がaxios経由で行えるようになりました。

## まとめ

もうネタバレしちゃいますが、今、ホスティングシステムを作っています。そろそろ0.0.1バージョンはリリースできそうなので、しっかりコミットしていきます。

## シリーズ

[Part4](https://qiita.com/himrock922/items/563d52a5a9c74ee4e25e) ← [Part5](https://qiita.com/himrock922/items/001d1a58d7ed61270bf7) → [Part6](https://qiita.com/himrock922/items/5ea559e8d006449f6145)
## 参考文献

* [verbatim](https://docs.djangoproject.com/en/2.1/ref/templates/builtins/#verbatim)
* [インストール](https://jp.vuejs.org/v2/guide/installation.html)
* [コンポーネントの基本](https://jp.vuejs.org/v2/guide/components.html)
* [単一ファイルコンポーネント](https://jp.vuejs.org/v2/guide/single-file-components.html)
* [axios](https://jp.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html)
* [https://www.techiediaries.com/django-react-forms-csrf-axios/](https://www.techiediaries.com/django-react-forms-csrf-axios/)
* [https://jp.vuejs.org/v2/guide/components-props.html](https://jp.vuejs.org/v2/guide/components-props.html)