Dynamic updates in real time to a django template

安稳与你 提交于 2020-08-21 07:34:06

问题


I'm building a django app that will provide real time data. I'm fairly new to Django, and now i'm focusing on how to update my data in real time, without having to reload the whole page.

Some clarification: the real time data should be update regularly, not only through a user input.

View

def home(request):

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

    test = tst['lastPrice']

    context={"test":test}

    return render(request,
                  "main/home.html", context
                  )

Template

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

I already asked this question, but i'm having some doubts:

I've been told to use Ajax, and that's ok, but is Ajax good for this case, where i will have a page loaded with data updated in real time every x seconds?

I have also been told to use DRF (Django Rest Framework). I've been digging through it a lot, but what it's not clear to me is how does it work with this particular case.


回答1:


Here below, I'm giving a checklist of the actions needed to implement a solution based on Websocket and Django Channels, as suggested in a previous comment. The motivation for this are given at the end.

1) Connect to the Websocket and prepare to receive messages

On the client, you need to execute the follwing javascript code:

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

Here, we open the Websocket, and later elaborate the notifications sent by the server in the onmessage callback.

Possible improvements:

  • support SSL connections
  • use ReconnectingWebSocket: a small wrapper on WebSocket API that automatically reconnects
    <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) Install and configure Django Channels and Channel Layers

To configure Django Channels, follow these instructions:

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

Channel Layers is an optional component of Django Channels which provides a "group" abstraction which we'll use later; you can follow the instructions given here:

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

3) Publish the Websocket endpoint

Routing provides for Websocket (and other protocols) a mapping between the published endpoints and the associated server-side code, much as urlpattens does for HTTP in a traditional Django project

file 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) Write the consumer

The Consumer is a class which provides handlers for Websocket standard (and, possibly, custom) events. In a sense, it does for Websocket what a Django view does for HTTP.

In our case:

  • websocket_connect(): we accept the connections and register incoming clients to the "ticks" group
  • websocket_disconnect(): cleanup by removing che client from the group
  • new_ticks(): our custom handler which broadcasts the received ticks to it's Websocket client
  • I assume TICKS_GROUP_NAME is a constant string value defined in project's settings

file 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) And finally: broadcast the new ticks

For example:

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

where:

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

We need to enclose the call to group_send() in the async_to_sync() wrapper, as channel.layers provides only the async implementation, and we're calling it from a sync context. Much more details on this are given in the Django Channels documentation.

Notes:

  • make sure that "type" attribute matches the name of the consumer's handler (that is: 'new_ticks'); this is required
  • every client has it's own consumer; so when we wrote self.send() in the consumer's handler, that meant: send the data to a single client
  • here, we send the data to the "group" abstraction, and Channel Layers in turn will deliver it to every registered consumer

Motivations

Polling is still the most appropriate choice in some cases, being simple and effective.

However, on some occasions you might suffer a few limitations:

  • you keep querying the server even when no new data are available
  • you introduce some latency (in the worst case, the full period of the polling). The tradeoff is: less latency = more traffic.

With Websocket, you can instead notify the clients only when (and as soon as) new data are available, by sending them a specific message.




回答2:


AJAX calls and REST APIs are the combinations you are looking for. For real-time update of data, polling the REST API at regular intervals is the best option you have. Something like:

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

Now add Django Rest Framework to your project. They have a simple tutorial here. Create an API endpoint which will return the data as JSON, and use that URL in the AJAX call.

Now you might be confused because you passed in the data into the template as context, while rendering the page from your home view. Thats not going to work anymore. You'll have to add a script to update the value of the element like

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

where element_id is the id you give to the element, and "New Value" is the data you get from the response of the AJAX call.

I hope this gives you a basic context.



来源:https://stackoverflow.com/questions/56913676/dynamic-updates-in-real-time-to-a-django-template

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!