13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

DjangoでChannels 2.xを使ってWebSocketをした時に詰まったところ

Posted at

#はじめに
DjangoでWebSocketが使いたい時に、ググるとChannelsというライブラリが出てくると思います。
しかし、まだまだ公式ドキュメント以外の記事が少なく、ようやく見つけたと思ったらバージョンが1.xで記述が違う!ということに...
Channels 2.xを使った記事は本当に数少なく、さらにそれらの記事もチュートリアルをこなしたものだけという現状に絶望することになります。

この記事はWebSocketについての説明ではなく、チュートリアル以外のChannels 2.xの記事が増えることを願って、私が詰まったところと解決方法を残したいと思います。

環境
Python 3.6.0
Django 2.1.2
channels 2.1.5

#事前知識
いきなり丸投げですいませんが、とりあえずこちらの記事様を参考にしてチュートリアルをこなしていただければと思います。(その1からその4)

Django 2.0 + Channels 2.xを使ってWebSocketを扱う(その1)
http://www.denzow.me/entry/2018/03/25/235952

##redis
Channel Layerという複数のConsumer Instance間でメッセージを共有する仕組みがあります。この中のGroupという仕組みを使うことで、同じグループ内でメッセージのやりとりができます。

そしてChannel Layerを使うためにはsetting.pyに以下の記述が必要です。

mysite/setting.py
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            "hosts": [('redis', 6379)],
        },
    },
}

ここのhostにredisのサーバのホスト名と6379番ポート(多分redisのデフォルト)を記述します。
そして、__redisは別でインストール__しておきます。
(当然だろ、と思った方ばかりかとも思いますが、channel_redisを入れただけでいいと思っていたんですすいません)
Macであれば、brewでインストールできます。
試すだけならホスト名を"localhost"にして、ターミナルでredis-serverコマンドを叩けばredisが動きます。
ここを忘れていると、WebSocketがconnectしてからすぐに落ちます。注意

#やりたかったこと
私がやりたかったことは、スマートフォンからDjangoの方にデータをPOSTした時にWebSocketを介してWebページに自動で反映させるということです。
Channel 2.xではConsumerがクラスになっているのでViewからWebSocketを使うにはどうすればいいん?と詰まりました。

それではコードを

consumer.py
class Consumer(AsyncWebsocketConsumer):

    async def connect(self):
        # グループに入る
        await self.channel_layer.group_add(
            "test",  # グループ名
            self.channel_name  # チャンネル名
        )
        await self.accept()

    async def disconnect(self, close_code):
        # グループから出る
        await self.channel_layer.group_discard(
            "test",  # グループ名
            self.channel_name  # チャンネル名
        )

    async def chat_message(self, event):
        message = event['message']

        # メッセージを送る
        await self.send(text_data=json.dumps({
            'message': message
        }))

view.pyは一部です。channelsを使う部分のみ

views.py
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
    .
    .
    .
    channel_layer = get_channel_layer()
    async_to_sync(channel_layer.group_send)("test", {  # グループ名
        "type": "chat_message",  # Consumerクラスのchat_messageメソッド
        "message": list  # 今回は[{},{},{}]のようなものを送った
    })
    .
    .
    .

まずconnect部分でグループに入ります。グループ名は同じグループにしたい人で共通。
そしてConsumer外でChannel Layerを使う方法についてです。

使いたい場所でget_channel_layer()で取得して、group_sendで送ります。
group_sendasync_to_syncで囲んでおいてください。(非同期のため)
肝心な中身ですが、(グループ名,辞書型)になっています。
辞書の中身は、typemessageが必要です。typeにはConsumerクラスで使うメソッド名(おそらく)、messageには送りたいものを記述します。

するとConsumerクラスのchat_messageeventとして届きます。そこから送るメッセージを受け取り、あとはjsonでdumpしてWebSocketに流すだけです。
受け手のJS側は上記ページ(その2)を見ていただければ詰まることはないと思います。
onmessage()で受け取ることができるかと思います。

#テンプレート内でのテーブル表示
ちなみにJSに送ったリストをテーブル表示させたいだけであれば、Tabulatorを使えばできます。
検索して見てください。バージョンによって書き方が違いますが、
(現行バージョンは4.1です)
↓こんな感じで使います。

var table = new Tabulator("#sample-table", {
                data: message,
                layout:"fitColumns",
                columns:[
                    {title:"ID", field:"id", sortable:true},
                    {title:"テキスト", field:"text"},
                    {title:"時間", field:"date", sortable:true}
                ]
            });

まあこの辺りは蛇足です。

#最後に

私はここまでくるのに何度も諦めそうになりました。
そのくらい記事がないのです。少しでも助けになれれば幸いです。

研究で使用したコードだったのでgithubにはあげてません。

ここまで見て頂きありがとうございました。

13
5
0

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
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?