Skip to content

Commit 54c52e3

Browse files
author
Mark Gibbs
committed
Live updating documentation
Documentation of the live updating feature, including the http endpoint and some comments on deployment. Added a curl example. Description of sending messages to Dash applications from within Django.
1 parent ceca5ae commit 54c52e3

File tree

5 files changed

+94
-29
lines changed

5 files changed

+94
-29
lines changed

demo/demo/plotly_apps.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def callback_liveIn_button_press(red_clicks, blue_clicks, green_clicks,
190190
serve_locally=True)
191191

192192
def _get_cache_key(state_uid):
193-
return "demo-liveout-s4-%s" % state_uid
193+
return "demo-liveout-s6-%s" % state_uid
194194

195195
def generate_liveOut_layout():
196196
'Generate the layout per-app, generating each tine a new uuid for the state_uid argument'
@@ -244,6 +244,18 @@ def callback_liveOut_pipe_in(named_count, state_uid, **kwargs):
244244
colour_set = [(None, 0, 100) for i in range(5)]
245245

246246
_, last_ts, prev = colour_set[-1]
247+
248+
# Loop over all existing timestamps and find the latest one
249+
if not click_timestamp or click_timestamp < 1:
250+
click_timestamp = 0
251+
252+
for _, the_colour_set in state.items():
253+
_, lts, _ = the_colour_set[-1]
254+
if lts > click_timestamp:
255+
click_timestamp = lts
256+
257+
click_timestamp = click_timestamp + 1000
258+
247259
if click_timestamp > last_ts:
248260
colour_set.append((user, click_timestamp, prev * random.lognormvariate(0.0, 0.1)),)
249261
colour_set = colour_set[-100:]
@@ -270,23 +282,28 @@ def callback_show_timeseries(internal_state_string, state_uid, **kwargs):
270282

271283
colour_series = {}
272284

285+
colors = {'red':'#FF0000',
286+
'blue':'#0000FF',
287+
'green':'#00FF00',
288+
'yellow': '#FFFF00',
289+
'cyan': '#00FFFF',
290+
'magenta': '#FF00FF',
291+
'black' : '#000000',
292+
}
293+
273294
for colour, values in state.items():
274295
timestamps = [datetime.fromtimestamp(int(0.001*ts)) for _, ts, _ in values if ts > 0]
275296
#users = [user for user, ts, _ in values if ts > 0]
276297
levels = [level for _, ts, level in values if ts > 0]
277-
colour_series[colour] = pd.Series(levels, index=timestamps).groupby(level=0).first()
298+
if colour in colors:
299+
colour_series[colour] = pd.Series(levels, index=timestamps).groupby(level=0).first()
278300

279301
df = pd.DataFrame(colour_series).fillna(method="ffill").reset_index()[-25:]
280302

281-
colors = {'red':'#FF0000',
282-
'blue':'#0000FF',
283-
'green':'#00FF00',
284-
}
285-
286303
traces = [go.Scatter(y=df[colour],
287304
x=df['index'],
288305
name=colour,
289-
line=dict(color=colors[colour]),
306+
line=dict(color=colors.get(colour,'#000000')),
290307
) for colour in colour_series]
291308

292309
return {'data':traces,

demo/demo/templates/base.html

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,20 @@
1313
<title>Django Plotly Dash Examples - {%block title%}{%endblock%}</title>
1414
</head>
1515
<body>
16-
<header>
17-
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
18-
<div class="navbar-nav">
19-
<a class="navbar-brand" href="#">
20-
<img src="{%static "demo/logo.svg"%}" alt="Logo"/>
21-
</a>
22-
{%block demo_items%}
23-
<a class="nav-item nav-link btn btn-lg" href="{%url "home"%}">Contents</a>
24-
<a class="nav-item nav-link btn btn-lg" href="{%url "demo-one"%}">Demo One - Simple Use</a>
25-
<a class="nav-item nav-link btn btn-lg" href="{%url "demo-two"%}">Demo Two - Initial State</a>
26-
<a class="nav-item nav-link btn btn-lg" href="{%url "demo-three"%}">Demo Three - Enhanced Callbacks</a>
27-
<a class="nav-item nav-link btn btn-lg" href="{%url "demo-four"%}">Demo Four - Live Updating</a>
28-
{%endblock%}
29-
</div>
30-
</nav>
31-
</header>
32-
<main>
33-
<div class="container">
34-
{%block content%}{%endblock%}
16+
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
17+
<div class="navbar-nav">
18+
<a class="navbar-brand" href="#">
19+
<img src="{%static "demo/logo.svg"%}" alt="Logo"/>
20+
</a>
21+
<a class="nav-item nav-link btn btn-lg" href="{%url "home"%}">Contents</a>
22+
<a class="nav-item nav-link btn btn-lg" href="{%url "demo-one"%}">Demo One - Simple Use</a>
23+
<a class="nav-item nav-link btn btn-lg" href="{%url "demo-two"%}">Demo Two - Initial State</a>
24+
<a class="nav-item nav-link btn btn-lg" href="{%url "demo-three"%}">Demo Three - Enhanced Callbacks</a>
25+
<a class="nav-item nav-link btn btn-lg" href="{%url "demo-four"%}">Demo Four - Live Updating</a>
26+
27+
<a class="nav-item nav-link btn btn-lg"
28+
target="_blank"
29+
href="https://django-plotly-dash.readthedocs.io/en/latest/">Online Documentation</a>
3530
</div>
3631
</main>
3732
{%block footer%}

django_plotly_dash/urls.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
from .app_name import app_name, main_view_label
3333

34+
from .views import add_to_session
35+
3436
urlpatterns = [
3537
path('instance/<slug:ident>_dash-routes', routes, name="routes"),
3638
path('instance/<slug:ident>_dash-layout', layout, name="layout"),
@@ -50,4 +52,6 @@
5052
path('app/<slug:ident>', main_view, {'stateless':True}, name='app-%s'%main_view_label),
5153
path('app/<slug:ident>_dash-component-suites/<slug:component>/<resource>',
5254
component_suites, {'stateless':True}, name='app-component-suites'),
55+
56+
path('session', add_to_session, name="session-variable-example"),
5357
]

django_plotly_dash/views.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,17 @@ def component_suites(request, resource=None, component=None, **kwargs):
106106
redone_url = "/static/dash/%s/%s" %(component, resource)
107107

108108
return HttpResponseRedirect(redirect_to=redone_url)
109+
110+
111+
from django.template.response import TemplateResponse
112+
113+
def add_to_session(request, template_name="index.html", **kwargs):
114+
'Add some info to a session in a place that django-plotly-dash can pass to a callback'
115+
116+
django_plotly_dash = request.session.get("django_plotly_dash", dict())
117+
118+
session_add_count = django_plotly_dash.get('add_counter',0)
119+
django_plotly_dash['add_counter'] = session_add_count + 1
120+
request.session['django_plotly_dash'] = django_plotly_dash
121+
122+
return TemplateResponse(request, template_name, {})

docs/updating.rst

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ The messages are constrained to be JSON serialisable, as that is how they are tr
1212
also be as small as possible given that they travel from the server, to each interested client, and then back to the
1313
server again as an argument to one or more callback functions.
1414

15+
The round-trip of the message is a deliberate design choice, in order to enable the value within the message to be treated
16+
as much as possible like any other piece of data within a ``Dash`` application. This data is essentially stored
17+
on the client side of the client-server split, and passed to the server when each callback is invoked; note that this also
18+
encourages designs that keep the size of in-application data small. An
19+
alternative approach, such as directly invoking
20+
a callback in the server, would require the server to maintain its own copy of the application state.
21+
1522
Live updating requires a server setup that is considerably more
1623
complex than the alternative, namely use of the built-in `Interval <https://dash.plot.ly/live-updates>`_ component. However, live
1724
updating can be used to reduce server load (as callbacks are only made when needed) and application latency (as callbacks are
@@ -64,10 +71,38 @@ introduces another indeterminacy.
6471
HTTP Endpoint
6572
-------------
6673

67-
There is an HTTP endpoint that allows direct insertion of messages into a message channel.
74+
There is an HTTP endpoint, configured with
75+
the ``http_route`` option, that allows direct insertion of messages into a message channel. It is a
76+
direct equivalent of calling the ``send_to_pipe_channel`` function, and
77+
expects the ``channel_name``, ``label`` and ``value`` arguments to be provided in a JSON-encoded
78+
dictionary.
79+
80+
.. code-block:: bash
81+
82+
curl -d '{"channel_name":"live_button_counter",
83+
"label":"named_counts",
84+
"value":{"click_colour":"cyan"}}'
85+
http://localhost:8000/dpd/views/poke/
86+
87+
This will cause the (JSON-encoded) ``value`` argument to be sent on the ``channel_name`` channel with
88+
the given ``label``.
89+
90+
The provided endpoint skips any CSRF checks
91+
and does not perform any security checks such as authentication or authorisation, and should
92+
be regarded as a starting point for a more complete implementation if exposing this functionality is desired. On the
93+
other hand, if this endpoint is restricted so that it is only available from trusted sources such as the server
94+
itself, it does provide a mechanism for Django code running outside of the ASGI server, such as in a WSGI process or
95+
Celery worker, to push a message out to running applications.
6896

6997
Deployment
7098
----------
7199

72-
Need redis and daphne.
100+
The live updating feature needs both Redis, as it is the only supported backend at present for v2.0 and up of
101+
Channels, and Daphne or any other ASGI server for production use. It is also good practise to place the server(s) behind
102+
a reverse proxy such as Nginx; this can then also be configured to serve Django's static files.
103+
104+
A further consideration is the use of a WSGI server, such as Gunicorn, to serve the non-asynchronous subset of the http
105+
routes, albeit at the expense of having to separately manage ASGI and WSGI servers. This can be easily achieved through selective
106+
routing at the reverse proxy level, and is the driver behind the ``ws_route`` configuration option.
73107

108+
In passing, note that the demo also uses Redis as the caching backend for Django.

0 commit comments

Comments
 (0)