实时动态更新 django 模板

Jac*_*022 5 python django ajax django-templates django-views

我正在构建一个将提供实时数据的 django 应用程序。我对 Django 相当陌生,现在我专注于如何实时更新我的​​数据,而不必重新加载整个页面。

一些澄清:实时数据应该定期更新,而不仅仅是通过用户输入。

看法

def home(request):

    symbol = "BTCUSDT"
    tst = client.get_ticker(symbol=symbol)

    test = tst['lastPrice']

    context={"test":test}

    return render(request,
                  "main/home.html", context
                  )
Run Code Online (Sandbox Code Playgroud)

模板

<h3> var: {{test}} </h3>
Run Code Online (Sandbox Code Playgroud)

我已经问过这个问题,但我有一些疑问:

有人告诉我使用 Ajax,这没问题,但是 Ajax 是否适合这种情况,我将每 x 秒加载一个包含实时更新数据的页面?

我还被告知使用 DRF(Django Rest Framework)。我一直在深入研究它,但我不清楚它是如何处理这个特殊情况的。

Mar*_*ndi 36

在下面,我给出了实现基于 Websocket 和 Django Channels 的解决方案所需的操作清单,如之前的评论中所建议的。最后给出了这样做的动机。

1)连接Websocket,准备接收消息

在客户端,您需要执行以下 javascript 代码:

<script language="javascript">
    var ws_url = 'ws://' + window.location.host + '/ws/ticks/';
    var ticksSocket = new WebSocket(ws_url);

    ticksSocket.onmessage = function(event) {
        var data = JSON.parse(event.data);
        console.log('data', data);
        // do whatever required with received data ...
    };
</script>
Run Code Online (Sandbox Code Playgroud)

在这里,我们打开了Websocket,后面再详细说明服务器在onmessage回调中发送的通知。

可能的改进:

  • 支持 SSL 连接
  • 使用 ReconnectingWebSocket:WebSocket API 上的一个小包装器,可自动重新连接
    <script language="javascript">
        var prefix = (window.location.protocol == 'https:') ? 'wss://' : 'ws://';
        var ws_url = prefix + window.location.host + '/ws/ticks/';
        var ticksSocket = new ReconnectingWebSocket(ws_url);
        ...
    </script>
Run Code Online (Sandbox Code Playgroud)

2) 安装和配置 Django Channels 和 Channel Layers

要配置 Django 频道,请按照以下说明操作:

https://channels.readthedocs.io/en/latest/installation.html

Channel Layers 是 Django Channels 的一个可选组件,它提供了一个我们稍后将使用的“组”抽象;您可以按照此处给出的说明进行操作:

https://channels.readthedocs.io/en/latest/topics/channel_layers.html#

3)发布Websocket端点

路由为 Websocket(和其他协议)提供了已发布端点和相关服务器端代码之间的映射,就像 urlpattens 在传统 Django 项目中为 HTTP 所做的那样

文件 routing.py

from django.urls import path
from channels.routing import ProtocolTypeRouter, URLRouter
from . import consumers

application = ProtocolTypeRouter({
    "websocket": URLRouter([
        path("ws/ticks/", consumers.TicksSyncConsumer),
    ]),
})
Run Code Online (Sandbox Code Playgroud)

4)写入消费者

Consumer 是一个为 Websocket 标准(也可能是自定义)事件提供处理程序的类。从某种意义上说,它对 Websocket 的作用就像 Django 视图对 HTTP 所做的那样。

在我们的例子中:

  • websocket_connect():我们接受连接并将传入的客户端注册到“ticks”组
  • websocket_disconnect():通过从组中删除客户端进行清理
  • new_ticks():我们的自定义处理程序,它将接收到的滴答声广播到它的 Websocket 客户端
  • 我假设 TICKS_GROUP_NAME 是在项目设置中定义的常量字符串值

文件consumers.py

from django.conf import settings
from asgiref.sync import async_to_sync
from channels.consumer import SyncConsumer

class TicksSyncConsumer(SyncConsumer):

    def websocket_connect(self, event):
        self.send({
            'type': 'websocket.accept'
        })

        # Join ticks group
        async_to_sync(self.channel_layer.group_add)(
            settings.TICKS_GROUP_NAME,
            self.channel_name
        )

    def websocket_disconnect(self, event):
        # Leave ticks group
        async_to_sync(self.channel_layer.group_discard)(
            settings.TICKS_GROUP_NAME,
            self.channel_name
        )

    def new_ticks(self, event):
        self.send({
            'type': 'websocket.send',
            'text': event['content'],
        })
Run Code Online (Sandbox Code Playgroud)

5)最后:广播新的滴答声

例如:

ticks = [
    {'symbol': 'BTCUSDT', 'lastPrice': 1234, ...},
    ...
]
broadcast_ticks(ticks)
Run Code Online (Sandbox Code Playgroud)

在哪里:

import json
from asgiref.sync import async_to_sync
import channels.layers

def broadcast_ticks(ticks):
    channel_layer = channels.layers.get_channel_layer()
    async_to_sync(channel_layer.group_send)(
        settings.TICKS_GROUP_NAME, {
            "type": 'new_ticks',
            "content": json.dumps(ticks),
        })
Run Code Online (Sandbox Code Playgroud)

我们需要将调用包含group_send()async_to_sync()包装器中,因为 channel.layers 仅提供异步实现,并且我们从同步上下文调用它。Django Channels 文档中提供了更多详细信息。

笔记:

  • 确保“type”属性与消费者处理程序的名称匹配(即:'new_ticks');这是必需的
  • 每个客户都有自己的消费者;所以当我们在消费者的处理程序中编写 self.send() 时,这意味着:将数据发送到单个客户端
  • 在这里,我们将数据发送到“组”抽象,然后通道层将把它传递给每个注册的消费者

动机

在某些情况下,轮询仍然是最合适的选择,简单而有效。

但是,在某些情况下,您可能会遇到一些限制:

  • 即使没有新数据可用,您也会继续查询服务器
  • 你引入了一些延迟(在最坏的情况下,轮询的整个时期)。权衡是:更少的延迟 = 更多的流量。

使用 Websocket,您可以改为仅在有新数据可用时(并尽快)通过向客户端发送特定消息来通知客户端。