DjangoとVueでカンバンアプリケーションを作る(5)

※はじめからは[こちら](https://qiita.com/denzow/items/046f3c8b9bd8d3378eb4#_reference-7b3e4a58510793f8a9c7)

前回は作成済のボードの一覧画面を作りました。今回は、ボードの追加機能を実装します。


### サーバサイドの実装

まずはボードを追加するAPIを作っていきます。

#### サービスレイヤーの実装

まずはボードを追加するメソッドを用意します。

```diff:application/modules/kanban/service.py
@@ -3,3 +3,16 @@ from .models import Board

 def get_board_list_by_owner(owner):
     return Board.get_list_by_owner(owner=owner)
+
+
+def add_board(owner, board_name):
+    """
+    :param User owner:
+    :param str board_name:
+    :return:
+    """
+    board = Board.objects.create(
+        owner=owner,
+        name=board_name
+    )
+    return board
```

`add_board`に作成者とボード名を渡すことで`Board`モデルを生成するようにします。

#### viewの実装

APIのためのViewを実装します。postで必要なパラメータを受け取ったらボードを生成するようにしますので、 `BoardListApi`に`post`メソッドを追加します。

```diff:application/views/api/boards.py
@@ -1,3 +1,5 @@
+import json
+
 from django.http import JsonResponse
 from django.views.generic import View

class BoardListApi(View):

    def get(self, request):
        """
        ボードの一覧を戻す
        """
        board_list = []
        for board in kanban_sv.get_board_list_by_owner(request.user):
            board_list.append({
                'id': board.id,
                'name': board.name,
            })
         return JsonResponse({
             'board_list': board_list,
         })

+    def post(self, request):
+        """
+        新しいボードを追加する
+        """
+        data = json.loads(request.body)
+        board_name = data.get('boardName')
+        board = kanban_sv.add_board(
+            owner=request.user,
+            board_name=board_name
+        )
+        return JsonResponse({
+            'board_data': {
+                'id': board.id,
+                'name': board.name,
+            }
+        })
```

さて、これだけだと実行時にエラーが出ます。Djangoのデフォルトの設定では[CSRF対策](https://docs.djangoproject.com/ja/2.0/ref/csrf/)が有効化されています。APIでCSRFを考慮するのは面倒なので、解除してしまいます。


```diff:application/views/api/boards.py
@@ -1,11 +1,14 @@
 import json

 from django.http import JsonResponse
+from django.views.decorators.csrf import csrf_exempt
+from django.utils.decorators import method_decorator
 from django.views.generic import View

 from modules.kanban import service as kanban_sv


+@method_decorator(csrf_exempt, name='dispatch')
 class BoardListApi(View):

     def get(self, request):
```

`csrf_exempt`というデコレータをつけると、そのViewについてはCSRF対策が無効化されます。Djangoのクラスベースビューを使ってる場合は、`method_decorator`を使う必要があるので上のようなコードになります。厳密には、`get`や`post`に処理を引き渡す`dispatch`というメソッドがデフォルトでは定義されており、そこに`csrf_exempt`を付与するというコードです。お作法のようなものです。


#### urls.pyに割当

今回は、urls.pyへの割当は不要です。前回定義した以下の割当があります。

```python:application/views/urls.py
    path('api/boards/', login_required(BoardListApi.as_view())),
```

`/api/boads`へのGETアクセスでボードの一覧を取得していました。今回は同じURLにPOSTアクセスしてボードを作るので、URL自体は増えないため、マッピングの追加は不要です。

### クライアントサイドの実装
#### 追加ボタンコンポーネントの作成

ボード一覧の最後に追加ボタンを用意したいと思います。デザインは揃えたいので、前回作成した`BoardCard.vue`をもとに`AddBoardCard.vue`を以下の様に作ります。


```vue:application/vuejs/src/pages/Home/components/AddBoardCard.vue
<template>
  <a href="#">
    <div class="card bg-dark text-white">
      <div class="card-body">
        <h5 class="card-title">Add Board(+)</h5>
      </div>
    </div>
  </a>
</template>

<script>
export default {
  name: 'AddBoardCard',
};
</script>
```

特に動きなどは何もまだ用意していないのでガワだけです。これをまずはHome画面に組み込みます。


```diff:application/vuejs/src/pages/Home/Index.vue
@@ -5,6 +5,7 @@
                  :title="board.name"
                  :boardId="board.id"
                  :key="board.id" />
+      <AddBoardCard class='board-card col-3' />
     </div>
   </div>
 </template>
@@ -12,6 +13,8 @@
 <script>
 import { createNamespacedHelpers } from 'vuex';
 import BoardCard from './components/BoardCard.vue';
+import AddBoardCard from './components/AddBoardCard.vue';
+

 const { mapState, mapActions } = createNamespacedHelpers('home');

@@ -19,6 +22,7 @@ export default {
   name: 'home',
   components: {
     BoardCard,
+    AddBoardCard,
   },
   computed: {
     ...mapState([
```

これで以下のように追加ボタンが表示されるようになりました。

![KANBAN-3.png](https://qiita-image-store.s3.amazonaws.com/0/44722/eab2621f-60d6-bd69-4be4-018051cdb5bf.png)

#### KanbanClientへの追加

ボード追加のAPIを使えるようにするため、`KanbanClient`に`addBoard`メソッドを追加します。

```diff:application/vuejs/utils/kanbanClient.js
@@ -54,6 +54,11 @@ class KanbanClient extends Client {
     return response.data.boardList;
   }

+  async addBoard({ boardName }) {
+    await this._post(`${this.baseUrl}/boards/`, {
+      boardName,
+    });
+  }
 }
```

#### storeへの組み込み

Storeから`addBoard`を使えるようにします。

```diff:application/vuejs/src/store/pages/home.js
@@ -10,6 +10,10 @@ const actions = {
     const boardList = await KanbanClient.getBoardList();
     commit('setBoardList', { boardList });
   },
+  async addBoard({ dispatch }, { boardName }) {
+    await KanbanClient.addBoard({ boardName });
+    dispatch('fetchBoardList');
+  },
 };
```

stateを更新せず、ボードの追加が完了したら`dispatch('fetchBoardList');`で`fetchBoardList`アクションを呼び出し、ボード一覧の再取得をするようにしています。

#### コンポーネントへの組み込み

先程作成した、`AddBoardCard.vue`に組み込みます。

```diff:application/vuejs/src/pages/Home/components/AddBoardCard.vue
@@ -1,5 +1,5 @@
 <template>
-  <a href="#">
+  <a href="#" @click="addBoardAction">
     <div class="card bg-dark text-white">
       <div class="card-body">
         <h5 class="card-title">Add Board(+)</h5>
@@ -9,7 +9,23 @@
 </template>

 <script>
+import { createNamespacedHelpers } from 'vuex';
+
+const { mapActions } = createNamespacedHelpers('home');
+
+
 export default {
   name: 'AddBoardCard',
+  methods: {
+    addBoardAction() {
+      const boardName = window.prompt('BoardName?');
+      if (boardName) {
+        this.addBoard({
+          boardName,
+        });
+      }
+    },
+    ...mapActions(['addBoard']),
+  },
 };
 </script>
```

`@click="addBoardAction"`でこのコンポーネント自体がクリックされた際に、`methods`に定義している`addBoardAction`を呼び出すようにしました。この中では、ボード名の入力を促し、受け付けた後は先程Storeに定義した`addBoard`を呼び出しています。

これでボードの作成機能は実装完了です。以下のようにボードが追加できる様になっているはずです。

[![Image from Gyazo](https://i.gyazo.com/4a0e857fe3c5158016f147f7e7a9e6b3.gif)](https://gyazo.com/4a0e857fe3c5158016f147f7e7a9e6b3)

### 次回

https://qiita.com/denzow/items/45606026ca8269917124