LoginSignup
36
44

More than 3 years have passed since last update.

DjangoのテンプレートにVue.jsを導入してaxiosでAjaxしてみた話(前編)

Last updated at Posted at 2019-04-11

はじめに

Vue.jsが便利そうなのでDjangoと組み合わせて使ってみようと試してみた.使ってみたいといっても,大それたことしたいわけではなく,まずはDjangoのテンプレートの中にVue.jsのスクリプトを埋め込んでクライアント側でちょこっとした機能を実現できるようになるあたりまでが目標だ.

架空のTodoアプリを具体例として話を進めていくが,これはあくまで例示のためで,アプリ自体は実用には程遠いので悪しからず...

全体のコードはまとめてGitHubに置いた.

Django側での雛形の作成

最初に,Todoアプリに登録したいタスクのモデルを設計しておく.具体的には,次のように,タスクの名称(task),納期(due),完了済みかどうか(done)の3つのフィールドをもつデータモデル(Todo)を定義した.

class Todo(models.Model):
    task = models.CharField(max_length=255, unique=True)
    due = models.DateTimeField()
    done = models.BooleanField()

    def __str__(self):
        return self.task

後で利用するために,このデータモデルをadminサイトに登録し,adminサイトから架空のタスクをいくつか追加しておこう.

続いて,データベース内のタスクをすべて取得して納期順に並べて表示するページの雛形をつくる.具体的には,次のようなview関数

def show_todo(request):
    todos = models.Todo.objects.all().order_by("due")
    my_context = {
        "todos":todos,
        }
    return render(request, "name_of_your_template", my_context)

とテンプレート

<html>
  <head><title>Todo</title></head>
  <body>
    <h1>My Todo List</h1>
    <div>
      <ul>
        {% for item in todos %}
          <li>
            {% if item.done %}
              <span style="text-decoration: line-through; color: red">
                {{ item.task }}
                ({{ item.due|date:"n" }}/{{ item.due|date:"j" }})
              </span>
            {% else %}
              <span>
                {{ item.task }}
                ({{ item.due|date:"n" }}/{{ item.due|date:"j" }})
              </span>
            {% endif %}
          </li>
        {% endfor %}
      </ul>
    </div>
  </body>
</html>

を用意した.urlルーティングを適切に設定してページが表示されれば準備OKだ.

テンプレートへのVue.jsの導入

土台となる雛形ができたので,このテンプレートにVue.jsを導入していこう.簡単に試してみるには,次のように,CDNからロードするのが手っ取り早い.

<script src="https://unpkg.com/vue"></script>

今回はお試しなので,Vue.jsの具体的なスクリプトもこのテンプレートの中に直接書き込んでいくことにする.Vueのインスタンスを作成し,タスクの情報をリアクティブにするためにdata内のプロパティに取り込み,簡単な機能をまずひとつ実装してみた.

<html>
  <head><title>Todo</title></head>
  <body>
    <h1>My Todo List</h1>
    <div id="app">
      <ul v-cloak v-for="item in todos">
        <li v-on:click="changeStatus(item)">
          <span style="text-decoration: line-through; color: red" v-if="item.done">
            [[ item.task ]]
            ([[ item.due.getMonth()+1 ]]/[[ item.due.getDate() ]])
          </span>
          <span v-else>
            [[ item.task ]]
            ([[ item.due.getMonth()+1 ]]/[[ item.due.getDate() ]])
          </span>
        </li>
      </ul>
    </div>

    <script src="https://unpkg.com/vue"></script>
    <script>

var vm = new Vue({
  delimiters: ['[[', ']]'],
  el: '#app',
  data: {
    todos: []
  },
  mounted: function() {
    {% for item in todos %}
    this.todos.push({
      "task": "{{ item.task }}",
      "due": new Date({{ item.due|date:'U' }} * 1000),
      "done": {{ item.done|lower }},
    })
    {% endfor %}
  },
  methods: {
    changeStatus: function(item) {
      item.done = !item.done;
    }
  }
})

    </script>
  </body>
</html>

DjangoのテンプレートにVue.jsを導入する際には{{}}の扱いに注意する必要がある.デフォルトのままだと,双方でこの表記を使うことになるのでぶつかってしまう.これを回避するために個人的に気にいっている方法は,Vue.js側で{{}}を,例えば,[[]]に変更してしまうことだ.

これは,上の例にもあるように,Vueインスタンスを生成する際に次のように指定すればよい.

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

次に,mounted:の項目を見てほしい.まだAjax通信を実装していないので,苦肉の策として,Djangoからcontextとして渡されたtodosからタスクの情報をforループで取り出し,JavaScriptのオブジェクトに変換してからVue.js側のtodosに順に追加していっている.

ひとまずこれで動いた.この部分は,後でAjax通信に変更する.なお,考えてみれば当然だが,Vue.jsのスクリプトの中でDjangoのテンプレートタグが使えていることが確認できる.

html側のリスト表示(<ul></ul>)が,Djangoのcontextではなく,Vue.jsのdataからタスクの情報を引き出す形に変更されていることにも注意する.また,リスト要素(<li></li>)にクリックイベントを処理するディレクティブも追加してある.

これによって,リスト要素をクリックすると,methods:の項目で定義されているchangeStatusが呼び出され,対応するタスクのdoneのブール値が裏返る.

Ajax通信(その1,GET編)

続いて,Ajax通信を実装していこう.Vue.js自体にはAjax通信の機能は用意されていないらしいので,まずどういう策を取るかを決めなければならない.Vue.jsを導入していけはjQueryを使う機会は減っていくだろうから,Ajax通信のためだけにjQueryをロードするのは気が引ける.そういう場合にはaxiosがおすすめらしい(ので.それに従うことにする).

まず,Vue.jsの場合と同じように,axiosもCDNからロードしておく.

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

その上で,テンプレート内のVue.jsのスクリプトを下記のように更新する.

var vm = new Vue({
  delimiters: ['[[', ']]'],
  el: '#app',
  data: {
    todos: []
  },
  mounted: function() {
    axios.get('{% url "for_view_function_get_todo()" %}')
      .then(function (response) {
        for(var d in response.data) {
          var item = response.data[d];
          item.due = new Date(item.due);
          vm.todos.push(item);
        }
      })
      .catch(function (error) {
        console.log(error);
      })
      .then(function () {
      });
  },
  methods: {
    changeStatus: function(item) {
      item.done = !item.done;
    }
  }
})

mounted:の項目の中身がaxiosを利用したAjaxのGET通信に置き換わっている.中身を見ると,JSON形式で取得したタスクのリストをresponse.dataからforループで取り出し,順にtodosに追加していっていることがわかる.また,この際に,納期(due)は,JavaScriptのDateオブジェクトに変換している.

テンプレートの側はこれでOKだ.ただし,このAjaxリクエストに対して,Django側がレスポンスを返してくれないと話にならない.続いて,Django側の設定を行おう.

まずこのリクエストを処理するために,下記のようなview関数を用意する,

import json
from django.http import JsonResponse
from . import models

def get_todo(request):
    todos = models.Todo.objects.all().order_by("due").values()
    todolist = list(todos)
    return JsonResponse(todolist, safe=False)

そして,上のaxios.get()で,このview関数が呼ばれるようにurlルーティングを設定する.

実は,ここで詰まってかなりの時間を浪費した.ポイントは,クエリセットの末尾に.values()を追加すること,それをlist()でリストに変換してから返すこと,そしてJsonResponse()safe=Falseを指定することである(このページに助けられた,感謝).

なお,テンプレートの中ではもうDjango側のtodosは使わなくなったので,ページ全体の方のview関数でそれを作成してcontextに含める処理は不要になる.したがって,こちらのview関数は,例えば次のように簡略化してしまえる.

from django.views import generic

class ShowTodoView(generic.TemplateView):
    template_name = "todo/show_todo.html"

まとめ

前編では,Vue.jsをDjangoのテンプレートに導入し,Django側からAjaxでとってきたJSON形式の情報をVue.js側で取得して処理するところまでできた.話は後編に続く.後編では,逆に,Vue.js側の情報をDjango側に送ってデータベースを更新することを試す.

36
44
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
44