Django channels.

Basics of Python and Django. -> Настраниваем django channels для работы по веб-сокетам.

Настраниваем django channels для работы по веб-сокетам.

Установка.

mkdir app
cd app
python3 -m venv venv
. ./venv/bin/activate

Создаем requirements.txt

Django==3.0.7
channels
redis==3.2.0
channels_redis==2.4.2

Устанавливаем зависимости.

pip install -r requirements.txt

Создадим новый проект

django-admin startproject server

Создадим новое приложение connection.

cd server
./manage.py startapp connection

C моделью.

from django.db import models


class SocketConnection(models.Model):
    sid = models.CharField(max_length=250, db_index=True, null=True, unique=True)

    @staticmethod
    def create_if_not_exist(data):
        try:
            SocketConnection.objects.get(sid=data['sid'])
        except:
            SocketConnection.objects.create( \
                sid = data['sid'], 
            )

Еще мы создали статический метод для создания записи.

Админка.

from django.contrib import admin
from connection.models import SocketConnection

@admin.register(SocketConnection)
class SocketConnectionAdmin(admin.ModelAdmin):
    list_display = [
        'sid'
        ]

Для очистки таблицы при каждом запуске сервера добавим хук ready в connection/apps.py

ссылка на документацию

from django.apps import AppConfig

class ConnectionConfig(AppConfig):
    name = 'connection'
    def ready(self):
        from connection.models import SocketConnection
        SocketConnection.objects.all().delete()

И пропишем в connection/init.py

default_app_config = 'connection.apps.ConnectionConfig'

Далее подключаем все в settings.

INSTALLED_APPS = [
    ...
    'channels',
    'connection'
]

Создадим файл server/server/channels_app.py под приложение channels.

При этом наш проект называется server.

from channels.routing import ProtocolTypeRouter, URLRouter
from card.card_consumer import CardConsumer
from django.urls import re_path
from channels.auth import AuthMiddlewareStack


websocket_urlpatterns = [
    re_path(r'card/$', CardConsumer),
]

application = ProtocolTypeRouter({
    'websocket': AuthMiddlewareStack(
        URLRouter(
            websocket_urlpatterns
        )
    ), 
})

Создадим новое приложение card и включим его в settings.py

./manage.py startapp card

Далее в нем создадим класс консьюмера server/card/card_consumer.py, обслуживающего сокет соединения.

from channels.generic.websocket import WebsocketConsumer
import json
from asgiref.sync import async_to_sync
from connection.models import SocketConnection

class CardConsumer(WebsocketConsumer):

    sid = None;
    def connect(self):
        print('Connnect!!! %s' % self.channel_name)
        self.accept()
        self.sid = self.channel_name
        SocketConnection.create_if_not_exist({'sid': self.channel_name})


    def disconnect(self, close_code):
        print('DISCONNECT!!!')
        try:
            SocketConnection.objects.get(sid = self.sid).delete()
        except:
            pass


    def receive(self, text_data):
        message = json.loads(text_data)
        print(message)


    def test_hendler(self,event):
        message = {  \
            'type': 'test_hendler', \
            'message': 'test' \
        }
        self.send(text_data=json.dumps(message))

test_hendler - функция, которая посылает сообщение в сокет когда мы посылаем его из любой части джанго приложения методом:

async_to_sync(channel_layer.send)('socketId', {'type': 'test_hendler'})

Включим приложение в настройки.

ASGI_APPLICATION = "server.channels_app.application"

Добавим слой транспорта для channels.

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("127.0.0.1", 6379)],
        },
    },
}

При этом должен быть установлен REDIS сервер.

При запуске должны увидеть такую картину.

start page

Клиентская часть.

Определим папку для шаблонов в настройках.

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [ os.path.join(BASE_DIR, 'templates')],
    ...

Создадим шаблон server/templates/index.html.

<!DOCTYPE html>
<html>
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
</head>

<body>
    <h1>Test page</h1>
    <input type="text" id="text" />
    <button id="sendButton">Send message by JS</button>nt
    <a href="/send" target="_blank">Send message from server</a>
    <div id="messages"></div>
<script>

    var timer = null;
    connect = () => {

        clearInterval(timer);
        webSocket = new WebSocket('ws://localhost:7777/card/');

        webSocket.onerror = (evt) => {
            timer = setTimeout(connect,1000);
        }

        webSocket.onmessage = (event) => {
            var message = JSON.parse(event.data)
            $('#messages').append(message.message);
        }

        webSocket.onclose =  (event) => {
            console.log('Close connection');
            timer = setTimeout(connect,1000);
        };

        webSocket.onopen =  (event) => {
            console.log('Connection established');
        };
    }




    $('#sendButton').on('click', () => {
        var message = $('#text').val();
        var data = {
            'type': 'test_hendler',
            'data': message
        }
        webSocket.send(JSON.stringify(data));
    })

    connect();

</script>
</body>

</html>

Сделаем вьюху server/card/views.py

from django.shortcuts import render

def index(request):
    return render(request,'index.html')


from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from connection.models import SocketConnection

def send(request):
    channel_layer = get_channel_layer()
    for c in SocketConnection.objects.all():
        print('Sending to %s' % c.sid)
        async_to_sync(channel_layer.send)(c.sid, {'type': 'test_hendler'})

    return render(request,'send.html')

start page

start page