Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic updates in real time to a django template

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.

like image 826
Jack022 Avatar asked Jul 06 '19 11:07

Jack022


People also ask

What is Django dynamic?

dynamic-django-forms is a simple, reusable app that allows you to build (and respond to) dynamic forms, i.e. forms that have variable numbers and types of fields. A few examples of uses include: Building and sending out surveys. Job applications where each job might have a different application forms.

What does {% include %} do in Django?

The include tag allows you include a template inside the current template. This is useful when you have a block of content that are the same for many pages.

What does {% mean in Django?

{% %} is basically used when you have an expression and are called tags while {{ }} is used to simply access the variable.


1 Answers

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.

like image 149
Mario Orlandi Avatar answered Oct 10 '22 17:10

Mario Orlandi