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

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

前回でカードの並び替えはできるようになりました。今回はカードの追加を実装していきます。

### カードの追加

カードを追加できるようにしていきます。すでにボタン自体は追加済です。

![KANBAN-8.png](https://qiita-image-store.s3.amazonaws.com/0/44722/e7efe5f8-36d8-d34f-3142-bdb0ea99b807.png)

これを押した時のロジックを実装します。ポイントはこのロジックはWebsocket経由ではなく、RESTのようにAjaxで実装したほうが望ましいというのがポイントです。

#### Card追加のサービス実装

まずはサーバサイド側でCardの追加ロジックを実装します。カードはそのPipeLineの末尾に追加します。CardはPipeLine内での位置を`order`という属性で管理していますので、追加時はその最大の`order`よりも大きい値をセットします。`order`は連番であることを期待しているので、単にCardの数+1で良いはずです。

まずは、そのPipeLine内のCard数を戻すクエリを追加します。

```diff:application/modules/kanban/models/card.py
@@ -23,3 +23,7 @@ class Card(models.Model):
             return cls.objects.get(id=card_id)
         except cls.DoesNotExist:
             return None
+
+    @classmethod
+    def get_current_card_count_by_pipe_line(cls, pipe_line):
+        return cls.objects.filter(pipe_line=pipe_line).count()
```

そして、これを利用した`add_card`メソッドを追加します。

```diff:application/modules/kanban/service.py
@@ -63,3 +63,14 @@ def update_card_order(pipe_line_id, card_id_list):
         card.order = i
         card.pipe_line = pipe_line
         card.save()
+
+
+def add_card(pipe_line_id, card_title):
+    pipe_line = PipeLine.get_by_id(pipe_line_id)
+    current_count = Card.get_current_card_count_by_pipe_line(pipe_line)
+    card = Card(
+        title=card_title,
+        content=None,
+        pipe_line=pipe_line,
+        order=current_count + 1,  # 現在のカード数 + 1で末尾になるはず
+    )
+    card.save()
+    return card
```

#### add_card用のAPIを実装

`add_card`を呼び出すAPIとして`CardApi`を追加します。

```diff:application/views/api/boards.py
@@ -40,4 +40,27 @@ class BoardListApi(View):
                 'id': board.id,
                 'name': board.name,
             }
+
+
+@method_decorator(csrf_exempt, name='dispatch')
+class CardApi(View):
+
+    def post(self, request):
+        """
+        新しいcardを追加する
+        """
+        data = json.loads(request.body)
+        card_title = data.get('cardTitle')
+        pipe_line_id = data.get('pipeLineId')
+
+        card = kanban_sv.add_card(
+            pipe_line_id=pipe_line_id,
+            card_title=card_title
+        )
+        return JsonResponse({
+            'card_data': {
+                'id': card.id,
+                'name': card.title,
+            }
+        })
```

`cardTitle`と`pipeLineId`をパラメータとしてとるようにします。続いて、これをURLに割り当てます。

```diff:application/views/urls.py
@@ -7,7 +7,7 @@ from django.views.generic import TemplateView

 from .accounts import signup as signup_view
 from .api.accounts import AccountsApi
-from .api.boards import BoardListApi
+from .api.boards import BoardListApi, CardApi


 urlpatterns = [
@@ -17,6 +17,7 @@ urlpatterns = [

     path('api/accounts/', AccountsApi.as_view()),
     path('api/boards/', login_required(BoardListApi.as_view())),
+    path('api/cards/', login_required(CardApi.as_view())),
 ]
```

これで`api/cards/`にPOSTすればCardが作成されるはずです。

#### KanbanClientへの追加

フロント側からサーバサイドへアクセスする際に利用するAPIClient(`KanbanClient`)に`api/cards`用のメソッドを追加します。

```diff:application/vuejs/utils/kanbanClient.js
@@ -59,6 +59,13 @@ class KanbanClient extends Client {
       boardName,
     });
   }
+
+  async addCard({ cardTitle, pipeLineId }) {
+    await this._post(`${this.baseUrl}/boards/`, {
+      cardTitle,
+      pipeLineId,
+    });
+  }
 }
```

#### Store/Componentへの組み込み

KanbanClientを利用してAPIへアクセスするように組み込んでいきます。まずはStoreのActionを追加します。

```diff:application/vuejs/src/store/pages/board.js
@@ -1,4 +1,5 @@
 import camelcaseKeys from 'camelcase-keys';
+import kanbanClient from "../../../utils/kanbanClient";


 const state = {
@@ -31,6 +32,12 @@ const actions = {
     });
     commit('updateCardOrder', { pipeLineId, cardList });
   },
+  async addCard({ commit }, { pipeLineId, cardTitle }) {
+    await kanbanClient.addCard({
+      pipeLineId,
+      cardTitle,
+    });
+  },
 };
```

続いて、この`addBoard`を`PipeLine.vue`に組み込みます。

```diff:application/vuejs/src/pages/Board/components/BoardArea/PipeLine.vue
@@ -75,12 +75,19 @@ export default {
   methods: {
     addCardAction() {
       const cardTitle = window.prompt('CardTitle?');
+      if (cardTitle) {
+        this.addCard({
+          pipeLineId: this.pipeLine.pipeLineId,
+          cardTitle,
+        });
+      }
     },
     delPipeLineAction() {
       window.confirm(`DELETE [${this.pipeLineName}] ? Are you sure?`);
     },
     ...mapActions([
       'updateCardOrder',
+      'addCard',
     ]),
     ...mapGetters([
       'getBoardId',
```

これでカード追加ボタンを押すと、カードが追加されるようになりますが、追加されたカードはページをリロードするまで見えません。

![](https://i.gyazo.com/c9e58a125a552199e3378d677dc6b076.gif)

これは、カードが追加されたあとにボード全体のデータの再取得が行われていないためです。また、これは追加を行ったブラウザ以外に対してもデータの再取得をリクエストする必要もあります。

#### Card追加完了後のデータ再取得

カード追加完了後は、自身を含む全てのクライアントが新しいデータを取得する必要があります。これはちょうどカードの並び替えで行っている以下の部分と同じことです。

```python:application/views/ws/kanban_consumer.py
    def update_card_order(self, content):
        :
        async_to_sync(self.channel_layer.group_send)(
            self.room_group_name,
            {
                'type': 'send_board_data',
            }
        )
```

このChannelLayerを利用している処理単体をWebsocketを通じてクライアントが呼び出せるようにします。

```diff:application/views/ws/kanban_consumer.py
@@ -15,7 +15,8 @@ class KanbanConsumer(JsonWebsocketConsumer):
         self.board_id = None
         self.namespace = 'board'
         self.action_map = {
-            'update_card_order': self.update_card_order
+            'update_card_order': self.update_card_order,
+            'broadcast_board_data': self.broadcast_board_data
         }
         self.room_group_name = None

@@ -80,3 +81,16 @@ class KanbanConsumer(JsonWebsocketConsumer):
         if not action:
             raise Exception('{} is not a valid action_type'.format(content['type']))
         action(content)
+
+    def broadcast_board_data(self, content=None):
+        """
+        全クライアントに、ボードデータの再取得を依頼する
+        :param content:
+        :return:
+        """
+        async_to_sync(self.channel_layer.group_send)(
+            self.room_group_name,
+            {
+                'type': 'send_board_data',
+            }
+        )
```

これで、`broadcast_board_data`というメッセージをサーバ側に送信することで同じボードを開いている全クライアントのデータが更新できるようになります。

![](https://i.gyazo.com/796c8d170131fe5e40f09a6825279db7.gif)

これでカードの追加処理は実装完了です。

### 次回

カードの中身の変更とかを作っていきます。

https://qiita.com/denzow/items/03440f85748420f4b5e4