Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Denzow
September 18, 2018

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

PyConJP 2018の資料です。Django Channelsでカンバンアプリケーションを
作成した際の資料です。

関連資料:
https://qiita.com/denzow/items/046f3c8b9bd8d3378eb4
https://github.com/denzow/DjangoDeKanban

Denzow

September 18, 2018
Tweet

More Decks by Denzow

Other Decks in Technology

Transcript

  1. 3 ➡ でんぞう (@denzowill, denzow) ➡ scouty,inc シニアエンジニア ➡ Scrapy,

    Djangoあたり ➡ DBスペシャリスト(PostgreSQLが好き、•racleは得意だけど苦⼿) ➡ (元?)StartPythonClubスタッフ お前誰よ?
  2. インフラ構成図 Amazon
 DynamoD Amazon ECS Amazon ECS Amazon ECS Amazon

    ECS Amazon
 SQS Elastic Load AWS Lambda Amazon CloudWatch Amazon
 RDS Aurora
 (MySQL 5.7) Amazon 
 ElastiCache sns-activity
 watcher worker ϝΠϯαʔϏε Ϋϩʔϧͨ͠
 ੜσʔλͷdiff ੔ܗ͞Εͨσʔ λ event 
 (time- crawler
  3. インフラ構成図 Amazon
 DynamoD Amazon ECS Amazon ECS Amazon ECS Amazon

    ECS Amazon
 SQS Elastic Load AWS Lambda Amazon CloudWatch Amazon
 RDS Aurora
 (MySQL 5.7) Amazon 
 ElastiCache sns-activity
 watcher worker ϝΠϯαʔϏε Ϋϩʔϧͨ͠
 ੜσʔλͷdiff ੔ܗ͞Εͨσʔ λ event 
 (time- crawler
  4. インフラ構成図 Amazon
 DynamoD Amazon ECS Amazon ECS Amazon ECS Amazon

    ECS Amazon
 SQS Elastic Load AWS Lambda Amazon CloudWatch Amazon
 RDS Aurora
 (MySQL 5.7) Amazon 
 ElastiCache sns-activity
 watcher worker ϝΠϯαʔϏε Ϋϩʔϧͨ͠
 ੜσʔλͷdiff ੔ܗ͞Εͨσʔ λ event 
 (time- crawler
  5. 16

  6. 20

  7. 21

  8. 30

  9. 31

  10. 40 WSGIはWebSocketより前に⽣まれた def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) return

    [b'Hello World\n'] start_responseでステータスやヘッダを送信 サーバがwsgiのエンドポイントのcallableを呼び出す Callableはenvironとstart_responseを受け取る レスポンス本体をiterableな戻り値として返送
  11. 41 WSGIはWebSocketより前に⽣まれた def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) return

    [b'Hello World\n'] start_responseでステータスやヘッダを送信 1リクエスト毎にCallbleが呼ばれ 終了する サーバがwsgiのエンドポイントのcallableを呼び出す Callableはenvironとstart_responseを受け取る レスポンス本体をiterableな戻り値として返送
  12. 62 Django Channelsでの流れ(ProtocolTypeRouter) application/settings/base.py # ASGIの起点を指定 ASGI_APPLICATION = 'views.routing.application' application/views/routing.py

    from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter # Childのルーティングルールに分割 from .ws.routing import urlpatterns application = ProtocolTypeRouter({ # (http->django views is added by default) 'websocket': AuthMiddlewareStack( URLRouter( urlpatterns ) ), }) プロトコル毎の 処理振り分け
  13. 63 Django Channelsでの流れ(ProtocolTypeRouter) application/settings/base.py # ASGIの起点を指定 ASGI_APPLICATION = 'views.routing.application' application/views/routing.py

    from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter # Childのルーティングルールに分割 from .ws.routing import urlpatterns application = ProtocolTypeRouter({ # (http->django views is added by default) 'websocket': AuthMiddlewareStack( URLRouter( urlpatterns ) ), }) Websocketの場合 httpは書かなくても 勝⼿にurls.pyをもとに したものが追加される
  14. 64 Django Channelsでの流れ(ProtocolTypeRouter) application/settings/base.py # ASGIの起点を指定 ASGI_APPLICATION = 'views.routing.application' application/views/routing.py

    from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter # Childのルーティングルールに分割 from .ws.routing import urlpatterns application = ProtocolTypeRouter({ # (http->django views is added by default) 'websocket': AuthMiddlewareStack( URLRouter( urlpatterns ) ), }) Djangoの認証と 同じものを使えるように する
  15. 65 Django Channelsでの流れ(URLRouter) application/settings/base.py # ASGIの起点を指定 ASGI_APPLICATION = 'views.routing.application' application/views/routing.py

    from channels.auth import AuthMiddlewareStack from channels.routing import ProtocolTypeRouter, URLRouter # Childのルーティングルールに分割 from .ws.routing import urlpatterns application = ProtocolTypeRouter({ # (http->django views is added by default) 'websocket': AuthMiddlewareStack( URLRouter( urlpatterns ) ), }) どのようなURLにアクセス してきたかでの振り分け (別に直接書いてもいい)
  16. 66 Django Channelsでの流れ(URLRouter) application/views/ws/routing.py from django.urls import path from .consumers

    import kanban_consumer urlpatterns = [ path('ws/boards/<int:board_id>', kanban_consumer.KanbanConsumer) ] 基本的にはDjangoのurls.pyと同じ
  17. 67 Django Channelsでの流れ(URLRouter) application/views/ws/routing.py from django.urls import path from .consumers

    import kanban_consumer urlpatterns = [ path('ws/boards/<int:board_id>', kanban_consumer.KanbanConsumer) ] ws/boards/1 のような URLにマッチさせる 基本的にはDjangoのurls.pyと同じ
  18. 68 Django Channelsでの流れ(URLRouter) application/views/ws/routing.py from django.urls import path from .consumers

    import kanban_consumer urlpatterns = [ path('ws/boards/<int:board_id>', kanban_consumer.KanbanConsumer) ] 該当したときの処理 基本的にはDjangoのurls.pyと同じ
  19. 69 Django Channelsでの流れ(Consumer) application/views/ws/consumers/kanban_consumer.py : : class KanbanConsumer(BaseJsonConsumer): : async

    def connect(self): if not self.scope['user'].is_authenticated: await self.close() return self.user = self.scope['user'] self.board_id = self.scope['url_route']['kwargs']['board_id'] self.room_group_name = self.user.username await self.channel_layer.group_add( self.room_group_name, self.channel_name ) await self.accept() : Viewsみたいなもん
  20. 70 Django Channelsでの流れ(Consumer) application/views/ws/consumers/kanban_consumer.py : : class KanbanConsumer(BaseJsonConsumer): : async

    def connect(self): if not self.scope['user'].is_authenticated: await self.close() return self.user = self.scope['user'] self.board_id = self.scope['url_route']['kwargs']['board_id'] self.room_group_name = self.user.username await self.channel_layer.group_add( self.room_group_name, self.channel_name ) await self.accept() : AuthMiddlewareStackを 使うと、scope['user']に いれてくれる Viewsみたいなもん
  21. 71 Django Channelsでの流れ(Consumer) application/views/ws/consumers/kanban_consumer.py : : class KanbanConsumer(BaseJsonConsumer): : async

    def connect(self): if not self.scope['user'].is_authenticated: await self.close() return self.user = self.scope['user'] self.board_id = self.scope['url_route']['kwargs']['board_id'] self.room_group_name = self.user.username await self.channel_layer.group_add( self.room_group_name, self.channel_name ) await self.accept() : URLで <int:board_id> と 定義してた部分にマッチした情報が 取り出せる Viewsみたいなもん
  22. 72 application/views/ws/routing.py from django.urls import path from .consumers import kanban_consumer

    urlpatterns = [ path('ws/boards/<int:board_id>', kanban_consumer.KanbanConsumer) ] これ Django Channelsでの流れ(Consumer)
  23. 73 Django Channelsでの流れ(Consumer) application/views/ws/consumers/kanban_consumer.py : : class KanbanConsumer(BaseJsonConsumer): : async

    def connect(self): if not self.scope['user'].is_authenticated: await self.close() return self.user = self.scope['user'] self.board_id = self.scope['url_route']['kwargs']['board_id'] self.room_group_name = self.user.username await self.channel_layer.group_add( self.room_group_name, self.channel_name ) await self.accept() : URLで <int:board_id> と 定義してた部分にマッチした情報が 取り出せる Viewsみたいなもん
  24. 74 Django Channelsでの流れ(Consumer) application/views/ws/consumers/kanban_consumer.py : : class KanbanConsumer(BaseJsonConsumer): : async

    def connect(self): if not self.scope['user'].is_authenticated: await self.close() return self.user = self.scope['user'] self.board_id = self.scope['url_route']['kwargs']['board_id'] self.room_group_name = self.user.username await self.channel_layer.group_add( self.room_group_name, self.channel_name ) await self.accept() : ChannelLayerのgroup_addを 実⾏し、他のConsumerとの 通信ができるようにする Viewsみたいなもん
  25. 75 Django Channelsでの流れ(Consumer) application/views/ws/consumers/kanban_consumer.py : : class KanbanConsumer(BaseJsonConsumer): : async

    def connect(self): if not self.scope['user'].is_authenticated: await self.close() return self.user = self.scope['user'] self.board_id = self.scope['url_route']['kwargs']['board_id'] self.room_group_name = self.user.username await self.channel_layer.group_add( self.room_group_name, self.channel_name ) await self.accept() : 接続を受け⼊れる 接続を拒否する Viewsみたいなもん
  26. 76 もうちょっとConsumer class MyConsumer(AsyncJsonWebsocketConsumer or JsonWebsocketConsumer): async def connect(self): #

    accept or close if YourCondition: await self.accept() else: await self.close() async def receive_json(self, content, **kwargs): # receive message print(content) # echo back await self.send_json(content)
  27. 77 もうちょっとConsumer class MyConsumer(AsyncJsonWebsocketConsumer or JsonWebsocketConsumer): async def connect(self): #

    accept or close if YourCondition: await self.accept() else: await self.close() async def receive_json(self, content, **kwargs): # receive message print(content) # echo back await self.send_json(content) (Async)?JsonWebsocketConsumerを 継承して実装する
  28. 78 もうちょっとConsumer class MyConsumer(AsyncJsonWebsocketConsumer or JsonWebsocketConsumer): async def connect(self): #

    accept or close if YourCondition: await self.accept() else: await self.close() async def receive_json(self, content, **kwargs): # receive message print(content) # echo back await self.send_json(content) 初回接続時に 呼ばれる Accept or close を 呼び出す
  29. 79 もうちょっとConsumer class MyConsumer(AsyncJsonWebsocketConsumer or JsonWebsocketConsumer): async def connect(self): #

    accept or close if YourCondition: await self.accept() else: await self.close() async def receive_json(self, content, **kwargs): # receive message print(content) # echo back await self.send_json(content) クライアントからの メッセージ受信時に呼ばれる
  30. 80 もうちょっとConsumer class MyConsumer(AsyncJsonWebsocketConsumer or JsonWebsocketConsumer): async def connect(self): #

    accept or close if YourCondition: await self.accept() else: await self.close() async def receive_json(self, content, **kwargs): # receive message print(content) # echo back await self.send_json(content) クライアントへ メッセージを返送する
  31. 82 Client1 Consumer1 Client2 Client3 Consumer2 Consumer3 Consumer1の変更をClient2,3に伝えるには? card_list =

    [A] card_list = [A] card_list = [A] card_list = [A] card_list = [A] card_list = [A]
  32. 83 Client1 Consumer1 Client2 Client3 Add card B Consumer2 Consumer3

    Consumer1の変更をClient2,3に伝えるには? card_list = [A, B] card_list = [A, B] card_list = [A] card_list = [A] card_list = [A] card_list = [A]
  33. 84 Client1 Consumer1 Client2 Client3 Add card B Consumer2 Consumer3

    Consumer1の変更をClient2,3に伝えるには? card_list = [A, B] card_list = [A, B] card_list = [A] card_list = [A] card_list = [A] card_list = [A] Channel Layer
  34. 85 Client1 Consumer1 Client2 Client3 Add card B Consumer2 Consumer3

    Consumer1の変更をClient2,3に伝えるには? card_list = [A, B] card_list = [A, B] card_list = [A] card_list = [A] card_list = [A] card_list = [A] Channel Layer Plz get Get Get
  35. 86 Client1 Consumer1 Client2 Client3 Add card B Consumer2 Consumer3

    Consumer1の変更をClient2,3に伝えるには? card_list = [A, B] card_list = [A, B] card_list = [A] card_list = [A, B] card_list = [A] card_list = [A, B] Channel Layer Plz get Get Get
  36. 87 Client1 Consumer1 Client2 Client3 Add card B Consumer2 Consumer3

    Consumer1の変更をClient2,3に伝えるには? card_list = [A, B] card_list = [A, B] card_list = [A, B] card_list = [A, B] card_list = [A, B] card_list = [A, B] Channel Layer Plz get Get Get
  37. 88 もうちょっとChannelLayer class MyConsumer(AsyncJsonWebsocketConsumer or JsonWebsocketConsumer): async def connect(self): :

    self.group_name = 'group_1' await self.channel_layer.group_add( self.group_name, self.channel_name # auto injection ) async def receive_json(self, content, **kwargs): : # broadcast other consumers await self.channel_layer.group_send( self.group_name, { 'type': 're_send', } ) async def re_send(self, *args, **kwargs): await self.send_json('new_content')
  38. 89 もうちょっとChannelLayer class MyConsumer(AsyncJsonWebsocketConsumer or JsonWebsocketConsumer): async def connect(self): :

    self.group_name = 'group_1' await self.channel_layer.group_add( self.group_name, self.channel_name # auto injection ) async def receive_json(self, content, **kwargs): : # broadcast other consumers await self.channel_layer.group_send( self.group_name, { 'type': 're_send', } ) async def re_send(self, *args, **kwargs): await self.send_json('new_content') connect時に 処理内容を共有する グループへ登録
  39. 90 もうちょっとChannelLayer class MyConsumer(AsyncJsonWebsocketConsumer or JsonWebsocketConsumer): async def connect(self): :

    self.group_name = 'group_1' await self.channel_layer.group_add( self.group_name, self.channel_name # auto injection ) async def receive_json(self, content, **kwargs): : # broadcast other consumers await self.channel_layer.group_send( self.group_name, { 'type': 're_send', } ) async def re_send(self, *args, **kwargs): await self.send_json('new_content') group_sendで ChannelLayerを 通じて他のConsumerに リクエストする
  40. 91 もうちょっとChannelLayer class MyConsumer(AsyncJsonWebsocketConsumer or JsonWebsocketConsumer): async def connect(self): :

    self.group_name = 'group_1' await self.channel_layer.group_add( self.group_name, self.channel_name # auto injection ) async def receive_json(self, content, **kwargs): : # broadcast other consumers await self.channel_layer.group_send( self.group_name, { 'type': 're_send', } ) async def re_send(self, *args, **kwargs): await self.send_json('new_content') typeで指定したメソッドが 各Consumerで実⾏される
  41. 92 実際のコードでは application/views/ws/consumers/kanban_consumer.py def __init__(self, *args, **kwargs): : self.action_map =

    { 'update_card_order': self.update_card_order, 'update_pipe_line_order': self.update_pipe_line_order, 'add_pipe_line': self.add_pipe_line, 'add_card': self.add_card, 'rename_pipe_line': self.rename_pipe_line, 'delete_pipe_line': self.delete_pipe_line, 'delete_board': self.delete_board, 'rename_board': self.rename_board, 'broadcast_board_data': self.broadcast_board_data, 'broadcast_board_data_without_requester': self.broadcast_board_data_without_requester, }
  42. 93 実際のコードでは application/views/ws/consumers/base_consumer.py async def receive_json(self, content, **kwargs): """ Typeに応じた処理を呼び出して実⾏する

    :param dict content: :param kwargs: :return: """ action = self.action_map.get(content['type']) if not action: raise ConsumerException('{} is not a valid action_type'.format(content['type'])) await action(content) content.typeに応じたメソッドを 呼び出すようにreceive_jsonを オーバライド
  43. 107 VueNativeWebsocket 接続先の変更とかはちょっとだるい // 切断 Vue.prototype.$disconnect(); // InstallされているVueNativeSockを⼀旦削除する const index

    = Vue._installedPlugins.indexOf(VueNativeSock); if (index > -1) { Vue._installedPlugins.splice(index, 1); } // 新しいURLで再インストールする Vue.use(VueNativeSock, `/new_ws_endpoint/`, { connectManually: true, reconnection: true, reconnectionAttempts: 5, reconnectionDelay: 3000, format: 'json', store, }); // 新しいURLへ接続する Vue.prototype.$connect();
  44. 109

  45. 110 データの流れ(VueComponent) <Draggable class="card-container" :options="options" v-model="wrappedCardList" > <Card v-for="card in

    wrappedCardList" class="item" v-show="card.isShown" :card="card" :key="card.cardId" /> </Draggable> : computed: { wrappedCardList: { get() { return this.pipeLine.cardList; }, set(value) { console.log(value); this.updateCardOrder({ pipeLineId: this.pipeLine.pipeLineId, cardList: value, }); }, }, application/vuejs/src/pages/Board/components/BoardArea/PipeLine.vue
  46. 111 データの流れ(VueComponent) <Draggable class="card-container" :options="options" v-model="wrappedCardList" > <Card v-for="card in

    wrappedCardList" class="item" v-show="card.isShown" :card="card" :key="card.cardId" /> </Draggable> : computed: { wrappedCardList: { get() { return this.pipeLine.cardList; }, set(value) { console.log(value); this.updateCardOrder({ pipeLineId: this.pipeLine.pipeLineId, cardList: value, }); }, }, Draggableで 囲んだ要素(Card)が D&D可能に D&Dの更新内容は V-modelに同期される 作り application/vuejs/src/pages/Board/components/BoardArea/PipeLine.vue
  47. 112 データの流れ(VueComponent) <Draggable class="card-container" :options="options" v-model="wrappedCardList" > <Card v-for="card in

    wrappedCardList" class="item" v-show="card.isShown" :card="card" :key="card.cardId" /> </Draggable> : computed: { wrappedCardList: { get() { return this.pipeLine.cardList; }, set(value) { console.log(value); this.updateCardOrder({ pipeLineId: this.pipeLine.pipeLineId, cardList: value, }); }, }, D&D完了時にsetが 呼ばれる [ {cardId: 1, title: ....}, {cardId: 2, title: ....}, {cardId: 3, title: ....}, ] application/vuejs/src/pages/Board/components/BoardArea/PipeLine.vue
  48. 113 データの流れ(VueComponent) <Draggable class="card-container" :options="options" v-model="wrappedCardList" > <Card v-for="card in

    wrappedCardList" class="item" v-show="card.isShown" :card="card" :key="card.cardId" /> </Draggable> : computed: { wrappedCardList: { get() { return this.pipeLine.cardList; }, set(value) { console.log(value); this.updateCardOrder({ pipeLineId: this.pipeLine.pipeLineId, cardList: value, }); }, }, Vuexに処理を 依頼する application/vuejs/src/pages/Board/components/BoardArea/PipeLine.vue
  49. 114 データの流れ(Vuex) // ACTION updateCardOrder({ commit, getters }, { pipeLineId,

    cardList }) { const socket = getters.getSocket; socket.sendObj({ type: 'update_card_order', pipeLineId, cardIdList: cardList.map(x => x.cardId), }); commit('updateCardOrder', { pipeLineId, cardList }); }, // MUTATION updateCardOrder(state, { pipeLineId, cardList }) { const targetPipeLine = state.boardData.pipeLineList .find(pipeLine => pipeLine.pipeLineId === pipeLineId); targetPipeLine.cardList = cardList; }, application/vuejs/src/store/pages/board.js
  50. 115 データの流れ(Vuex) // ACTION updateCardOrder({ commit, getters }, { pipeLineId,

    cardList }) { const socket = getters.getSocket; socket.sendObj({ type: 'update_card_order', pipeLineId, cardIdList: cardList.map(x => x.cardId), }); commit('updateCardOrder', { pipeLineId, cardList }); }, // MUTATION updateCardOrder(state, { pipeLineId, cardList }) { const targetPipeLine = state.boardData.pipeLineList .find(pipeLine => pipeLine.pipeLineId === pipeLineId); targetPipeLine.cardList = cardList; }, application/vuejs/src/store/pages/board.js VueComponentから ここが呼ばれる。
  51. 116 データの流れ(Vuex) // ACTION updateCardOrder({ commit, getters }, { pipeLineId,

    cardList }) { const socket = getters.getSocket; socket.sendObj({ type: 'update_card_order', pipeLineId, cardIdList: cardList.map(x => x.cardId), }); commit('updateCardOrder', { pipeLineId, cardList }); }, // MUTATION updateCardOrder(state, { pipeLineId, cardList }) { const targetPipeLine = state.boardData.pipeLineList .find(pipeLine => pipeLine.pipeLineId === pipeLineId); targetPipeLine.cardList = cardList; }, application/vuejs/src/store/pages/board.js VueNativeWebsocketを 使って、Django側に更新を 依頼(後述)
  52. 117 データの流れ(Vuex) // ACTION updateCardOrder({ commit, getters }, { pipeLineId,

    cardList }) { const socket = getters.getSocket; socket.sendObj({ type: 'update_card_order', pipeLineId, cardIdList: cardList.map(x => x.cardId), }); commit('updateCardOrder', { pipeLineId, cardList }); }, // MUTATION updateCardOrder(state, { pipeLineId, cardList }) { const targetPipeLine = state.boardData.pipeLineList .find(pipeLine => pipeLine.pipeLineId === pipeLineId); targetPipeLine.cardList = cardList; }, application/vuejs/src/store/pages/board.js 投げると同時に投げた クライアント側では更新が ⾏われた体で表⽰を更新
  53. 118 データの流れ(Vuex) // ACTION updateCardOrder({ commit, getters }, { pipeLineId,

    cardList }) { const socket = getters.getSocket; socket.sendObj({ type: 'update_card_order', pipeLineId, cardIdList: cardList.map(x => x.cardId), }); commit('updateCardOrder', { pipeLineId, cardList }); }, // MUTATION updateCardOrder(state, { pipeLineId, cardList }) { const targetPipeLine = state.boardData.pipeLineList .find(pipeLine => pipeLine.pipeLineId === pipeLineId); targetPipeLine.cardList = cardList; }, application/vuejs/src/store/pages/board.js
  54. 120 データの流れ(Django Channels) class KanbanConsumer(BaseJsonConsumer): def __init__(self, *args, **kwargs): :

    self.action_map = { 'update_card_order': self.update_card_order, : } : application/views/ws/consumers/kanban_consumer.py type: 'update_card_order'は self.update_card_orderを 呼び出しするようにマップ
  55. 121 データの流れ(Django Channels) class KanbanConsumer(BaseJsonConsumer): async def update_card_order(self, content): """

    ボード内のカードの並び順を更新する """ pipe_line_id = content['pipeLineId'] card_id_list = content['cardIdList'] await database_sync_to_async(kanban_sv.update_card_order)( pipe_line_id, card_id_list ) await self.broadcast_board_data_without_requester() application/views/ws/consumers/kanban_consumer.py
  56. 122 データの流れ(Django Channels) class KanbanConsumer(BaseJsonConsumer): async def update_card_order(self, content): """

    ボード内のカードの並び順を更新する """ pipe_line_id = content['pipeLineId'] card_id_list = content['cardIdList'] await database_sync_to_async(kanban_sv.update_card_order)( pipe_line_id, card_id_list ) await self.broadcast_board_data_without_requester() application/views/ws/consumers/kanban_consumer.py DjangoORM経由でカードの 並び順を更新
  57. 123 データの流れ(Django Channels) class KanbanConsumer(BaseJsonConsumer): async def update_card_order(self, content): """

    ボード内のカードの並び順を更新する """ pipe_line_id = content['pipeLineId'] card_id_list = content['cardIdList'] await database_sync_to_async(kanban_sv.update_card_order)( pipe_line_id, card_id_list ) await self.broadcast_board_data_without_requester() application/views/ws/consumers/kanban_consumer.py 更新処理をした Consumer以外に ブロードキャスト
  58. 124 データの流れ(Django Channels) class KanbanConsumer(BaseJsonConsumer): async def update_card_order(self, content): """

    ボード内のカードの並び順を更新する """ pipe_line_id = content['pipeLineId'] card_id_list = content['cardIdList'] await database_sync_to_async(kanban_sv.update_card_order)( pipe_line_id, card_id_list ) await self.broadcast_board_data_without_requester() application/views/ws/consumers/kanban_consumer.py
  59. 125 // ACTION updateCardOrder({ commit, getters }, { pipeLineId, cardList

    }) { const socket = getters.getSocket; socket.sendObj({ type: 'update_card_order', pipeLineId, cardIdList: cardList.map(x => x.cardId), }); commit('updateCardOrder', { pipeLineId, cardList }); }, // MUTATION updateCardOrder(state, { pipeLineId, cardList }) { const targetPipeLine = state.boardData.pipeLineList .find(pipeLine => pipeLine.pipeLineId === pipeLineId); targetPipeLine.cardList = cardList; }, application/vuejs/src/store/pages/board.js 処理を依頼したクライアントは すでに更新できた体で 再描画済み
  60. 126 データの流れ(Django Channels) class KanbanConsumer(BaseJsonConsumer): : async def broadcast_board_data_without_requester(self, *args):

    await self.group_send( self.room_group_name, { 'type': 'send_board_data', 'requester_id': self.consumer_id, } ) application/views/ws/consumers/kanban_consumer.py
  61. 127 データの流れ(Django Channels) class KanbanConsumer(BaseJsonConsumer): : async def broadcast_board_data_without_requester(self, *args):

    await self.group_send( self.room_group_name, { 'type': 'send_board_data', 'requester_id': self.consumer_id, } ) application/views/ws/consumers/kanban_consumer.py channel_layerのgroup_sendで 他のConsumerにsend_board_dataの 実⾏を依頼
  62. 128 データの流れ(Django Channels) class KanbanConsumer(BaseJsonConsumer): : async def send_board_data(self, event):

    """ ボードのデータを送る """ # ⾃⾝が発⽕したブロードキャストなら無視する if event.get('requester_id') == self.consumer_id: return board_data = await database_sync_to_async( kanban_sv.get_board_data_board_id )(self.board_id) await self.send_data({ 'boardData': board_data, }, mutation='setBoardData') application/views/ws/consumers/kanban_consumer.py
  63. 129 データの流れ(Django Channels) class KanbanConsumer(BaseJsonConsumer): : async def send_board_data(self, event):

    """ ボードのデータを送る """ # ⾃⾝が発⽕したブロードキャストなら無視する if event.get('requester_id') == self.consumer_id: return board_data = await database_sync_to_async( kanban_sv.get_board_data_board_id )(self.board_id) await self.send_data({ 'boardData': board_data, }, mutation='setBoardData') application/views/ws/consumers/kanban_consumer.py Django ORM経由でボードの 構成データを再取得
  64. 130 データの流れ(Django Channels) class KanbanConsumer(BaseJsonConsumer): : async def send_board_data(self, event):

    """ ボードのデータを送る """ # ⾃⾝が発⽕したブロードキャストなら無視する if event.get('requester_id') == self.consumer_id: return board_data = await database_sync_to_async( kanban_sv.get_board_data_board_id )(self.board_id) await self.send_data({ 'boardData': board_data, }, mutation='setBoardData') application/views/ws/consumers/kanban_consumer.py 新しいボードデータを 対応するクライアントに送信
  65. 131 データの流れ(Django Channels) class KanbanConsumer(BaseJsonConsumer): : async def send_board_data(self, event):

    """ ボードのデータを送る """ # ⾃⾝が発⽕したブロードキャストなら無視する if event.get('requester_id') == self.consumer_id: return board_data = await database_sync_to_async( kanban_sv.get_board_data_board_id )(self.board_id) await self.send_data({ 'boardData': board_data, }, mutation='setBoardData') application/views/ws/consumers/kanban_consumer.py クライアント側の mutation:setBoardDataに 引き渡す
  66. 132 データの流れ(Vuex) const state = { boardData: { pipeLineList: [],

    }, focusedCard: {}, searchWord: '', }; // MUTATION setBoardData(state, { boardData }) { state.boardData = camelcaseKeys(boardData, { deep: true }); }, application/vuejs/src/store/pages/board.js
  67. 133 データの流れ(Vuex) const state = { boardData: { pipeLineList: [],

    }, focusedCard: {}, searchWord: '', }; // MUTATION setBoardData(state, { boardData }) { state.boardData = camelcaseKeys(boardData, { deep: true }); }, application/vuejs/src/store/pages/board.js 返送されたデータで 更新し画⾯に反映
  68. 134 データの流れ(まとめ) D&D(Client1) Vueで変更検知 Vuexに伝播 Consumerに依頼 ORMで更新 BroadCast send_data Vuexに反映

    再描画(Client1) Vuexに反映 再描画(Client2) send_data Vuexに反映 再描画(Client3)
  69. 138 Consumer復習 class MyConsumer(AsyncJsonWebsocketConsumer or JsonWebsocketConsumer): async def connect(self): #

    accept or close if YourCondition: self.accept() else: self.close() async def receive_json(self, content, **kwargs): # receive message print(content) # echo back await self.send_json(content)
  70. 139 Consumer復習 class MyConsumer(AsyncJsonWebsocketConsumer or JsonWebsocketConsumer): async def connect(self): #

    accept or close if YourCondition: self.accept() else: self.close() async def receive_json(self, content, **kwargs): # receive message print(content) # echo back await self.send_json(content) or ?
  71. 146 静かな凡ミス async def add_card(self, content): """ カードの追加 """ pipe_line_id

    = content['pipeLineId'] card_title = content['cardTitle'] database_sync_to_async(kanban_sv.add_card)( pipe_line_id, card_title ) self.broadcast_board_data() 間違い探し
  72. 147 静かな凡ミス async def add_card(self, content): """ カードの追加 """ pipe_line_id

    = content['pipeLineId'] card_title = content['cardTitle'] await database_sync_to_async(kanban_sv.add_card)( pipe_line_id, card_title ) await self.broadcast_board_data() 間違い探し
  73. 150 突然のMySQL Server has gone away async def hoge(self, event):

    # カードを取得 card = await database_sync_to_async(Card.objects.get)(id=1) # カードが所属するボードを取得して、その全体のデータを取得 board_data = await database_sync_to_async(kanban_sv.get_board_data)( card.board ) 間違い探し
  74. 151 突然のMySQL Server has gone away async def hoge(self, event):

    # カードを取得 card = await database_sync_to_async(Card.objects.get)(id=1) # カードが所属するボードを取得して、その全体のデータを取得 board_data = await database_sync_to_async(kanban_sv.get_board_data)( card.board ) 間違い探し FKをたどるだけでも、ORMの操作になる。 database_sync_to_asyncで囲わないと 接続がリークして、やがて死ぬ
  75. 157 あれ、Websocketで成功・失敗って・・・ async updateCardTitle({ commit, dispatch }, { boardId, cardId,

    title }) { const cardData = await KanbanClient.updateCardData({ boardId, cardId, title, }); dispatch('broadcastBoardData'); }, : broadcastBoardData({ getters }) { console.log('call broadcastBoardData'); const socket = getters.getSocket; socket.sendObj({ type: 'broadcast_board_data', }); },
  76. 158 あれ、Websocketで成功・失敗って・・・ async updateCardTitle({ commit, dispatch }, { boardId, cardId,

    title }) { const cardData = await KanbanClient.updateCardData({ boardId, cardId, title, }); dispatch('broadcastBoardData'); }, : broadcastBoardData({ getters }) { console.log('call broadcastBoardData'); const socket = getters.getSocket; socket.sendObj({ type: 'broadcast_board_data', }); }, Ajaxでカードを 更新(await)
  77. 159 あれ、Websocketで成功・失敗って・・・ async updateCardTitle({ commit, dispatch }, { boardId, cardId,

    title }) { const cardData = await KanbanClient.updateCardData({ boardId, cardId, title, }); dispatch('broadcastBoardData'); }, : broadcastBoardData({ getters }) { console.log('call broadcastBoardData'); const socket = getters.getSocket; socket.sendObj({ type: 'broadcast_board_data', }); }, Ajaxが終わったら 別の処理を発⽕
  78. 160 あれ、Websocketで成功・失敗って・・・ async updateCardTitle({ commit, dispatch }, { boardId, cardId,

    title }) { const cardData = await KanbanClient.updateCardData({ boardId, cardId, title, }); dispatch('broadcastBoardData'); }, : broadcastBoardData({ getters }) { console.log('call broadcastBoardData'); const socket = getters.getSocket; socket.sendObj({ type: 'broadcast_board_data', }); }, websocket経由で consumerに ブロードキャストを依頼