【问题标题】:Dynamic updates in real time to a django template实时动态更新 django 模板
【发布时间】:2019-11-16 17:09:15
【问题描述】:

我正在构建一个将提供实时数据的 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
                  )

模板

<h3> var: {{test}} </h3>

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

有人告诉我使用 Ajax,这没关系,但是 Ajax 是否适合这种情况,在这种情况下,我将加载一个页面,其中每 x 秒实时更新一次数据?

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

【问题讨论】:

  • 使用 DRF 创建端点以提供实时数据。让 ajax 从该端点获取数据并更新 h3 元素。

标签: python django ajax django-templates django-views


【解决方案1】:

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

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>

这里,我们打开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>

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),
    ]),
})

4) 写消费者

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

在我们的例子中:

  • websocket_connect():我们接受连接并将传入客户端注册到“ticks”组
  • websocket_disconnect():通过从组中删除 che 客户端进行清理
  • 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'],
        })

5) 最后:广播新的报价

例如:

ticks = [
    {'symbol': 'BTCUSDT', 'lastPrice': 1234, ...},
    ...
]
broadcast_ticks(ticks)

地点:

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),
        })

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

注意事项:

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

动机

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

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

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

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

【讨论】:

  • 嗨!刚遇到这个,我正在尝试使用频道。在这个特定的答案中,5)下的代码会去哪里?它位于views.py 中吗?如果我有一个现有的 websocket 连接到例如BitMEX,我怎么能把它连接起来,以便我可以使用这些更新而不是硬编码的“ticks”列表?我觉得我快到了,您的回答提供了很好的信息!
  • 您好@AaronScheib .. 对于您的第一个问题...据我了解原始帖子,实时数据应来自外部数据源;它与用户交互和 HTTP 请求/响应 cicle 无关,因此视图是毫无疑问的。我的建议是将 (5) 中的代码放在 Django 管理命令中(在生产服务器上运行,例如,通过 Supervisor);这样,您就有了“设置”以及所有可用的 Django 和 Django-Channels 环境。
  • 在管理命令中,我会创建一个无限循环来不断从外部数据源收集数据。收到的数据将在收到后立即通过 broadcast_ticks() 广播到您的网络客户端。
  • 这怎么只有一个赞成票?!非常感谢,马里奥!
  • 我刚刚回到这个答案,因为它在开发我的项目期间非常有用。惊人的! Grazie mille @MarioOrlandi
【解决方案2】:

AJAX 调用和 REST API 是您正在寻找的组合。对于数据的实时更新,定期轮询 REST API 是您拥有的最佳选择。比如:

function doPoll(){
    $.post('<api_endpoint_here>', function(data) {
        // Do operation to update the data here
        setTimeout(doPoll, <how_much_delay>);
    });
}

现在将 Django Rest Framework 添加到您的项目中。他们有一个简单的教程here。创建一个以 JSON 格式返回数据的 API 端点,并在 AJAX 调用中使用该 URL。

现在您可能会感到困惑,因为您将数据作为上下文传递到模板中,同时从您的home 视图呈现页面。那不再起作用了。您必须添加一个脚本来更新元素的值,例如

document.getElementById("element_id").value = "New Value";

其中element_id 是您为元素提供的ID,"New Value" 是您从AJAX 调用的响应中获得的数据。

我希望这能给你一个基本的背景。

【讨论】:

  • 感谢您的回答!例如,这种组合与使用 Django Channels 有什么区别?
  • 而且,当我必须更新页面中的大量数据(例如整个数据表)时,它会起作用吗?
  • @Jack022,轮询的限制是双重的:(1)在一个站点上,即使没有新数据可用,您也会继续查询服务器,以及(2)您必须引入一些延迟(在最坏的情况是整个轮询期间)。权衡是:更少的延迟=更多的流量。使用 django-channels + Websockets,您可以改为仅在(并且尽快)通过向客户发送特定消息来通知客户新价格可用。话虽如此,在某些情况下轮询仍然是合适的。如果您对 django-channels 选项感兴趣,我很乐意提供详细示例
猜你喜欢
  • 2019-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-19
  • 2015-08-16
  • 2012-03-29
相关资源
最近更新 更多