Skip to content

Commit

Permalink
Merge pull request #10 from delsim/async_use
Browse files Browse the repository at this point in the history
Prerelease version 0.4.0
  • Loading branch information
GibbsConsulting committed Jun 19, 2018
2 parents 578dcfe + 3205973 commit bcafcd4
Show file tree
Hide file tree
Showing 17 changed files with 254 additions and 13 deletions.
60 changes: 60 additions & 0 deletions demo/demo/consumers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
from channels.generic.websocket import WebsocketConsumer

import json

ALL_CONSUMERS = []

class MessageConsumer(WebsocketConsumer):
def __init__(self, *args, **kwargs):
super(MessageConsumer, self).__init__(*args, **kwargs)
global ALL_CONSUMERS
ALL_CONSUMERS.append(self)

def connect(self):
self.accept()

def disconnect(self, close_code):
ac = []
global ALL_CONSUMERS
for c in ALL_CONSUMERS:
if c != self:
ac.append(c)
ALL_CONSUMERS = ac

def send_to_widgets(self, channel_name, label, value):
message = json.dumps({'channel_name':channel_name,
'label':label,
'value':value})
global ALL_CONSUMERS

for c in ALL_CONSUMERS:
c.send(message)

def receive(self, text_data):
message = json.loads(text_data)

message_type = message.get('type','unknown_type')

if message_type == 'connection_triplet':

channel_name = message.get('channel_name',"UNNAMED_CHANNEL")
uid = message.get('uid',"0000-0000")
label = message.get('label','DEFAULT$LABEL')

# For now, send the uid as value. This essentially 'resets' the value
# each time the periodic connection announcement is made
self.send_to_widgets(channel_name=channel_name,
label=label,
value=uid)
else:
# Not a periodic control message, so do something useful
# For now, this is just pushing to all other consumers indiscrimnately

channel_name = message.get('channel_name',"UNNAMED_CHANNEL")
uid = message.get('uid',"0000-0000")
value = message.get('value',{'source_uid':uid})
label = message.get('label','DEFAULT$LABEL')

self.send_to_widgets(channel_name=channel_name,
label=label,
value=value)
32 changes: 32 additions & 0 deletions demo/demo/plotly_apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import dash_core_components as dcc
import dash_html_components as html

import dpd_components as dpd

from django_plotly_dash import DjangoDash

app = DjangoDash('SimpleExample')
Expand Down Expand Up @@ -52,3 +54,33 @@ def callback_c(*args,**kwargs):
da = kwargs['dash_app']
return "Args are [%s] and kwargs are %s" %(",".join(args),str(kwargs))

a3 = DjangoDash("Connected")

a3.layout = html.Div([
dpd.Pipe(id="dynamic",
value="Dynamo 123",
label="rotational energy",
channel_name="test_widget_channel",
uid="need_to_generate_this"),
dpd.Pipe(id="also_dynamic",
value="Alternator 456",
label="momentum",
channel_name="test_widget_channel",
uid="and_this_one"),
dpd.DPDirectComponent(id="direct"),
dcc.RadioItems(id="dropdown-one",options=[{'label':i,'value':j} for i,j in [
("O2","Oxygen"),("N2","Nitrogen"),("CO2","Carbon Dioxide")]
],value="Oxygen"),
html.Div(id="output-three")
])

@a3.expanded_callback(
dash.dependencies.Output('output-three','children'),
[dash.dependencies.Input('dynamic','value'),
dash.dependencies.Input('dynamic','label'),
dash.dependencies.Input('also_dynamic','value'),
dash.dependencies.Input('dropdown-one','value'),
])
def callback_a3(*args, **kwargs):
da = kwargs['dash_app']
return "Args are [%s] and kwargs are %s" %(",".join(args),str(kwargs))
10 changes: 10 additions & 0 deletions demo/demo/routing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack

from django.conf.urls import url

from .consumers import MessageConsumer

application = ProtocolTypeRouter({
'websocket': AuthMiddlewareStack(URLRouter([url('ws/channel', MessageConsumer),])),
})
9 changes: 8 additions & 1 deletion demo/demo/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
'django.contrib.messages',
'django.contrib.staticfiles',

'channels',

'django_plotly_dash.apps.DjangoPlotlyDashConfig',
]

Expand Down Expand Up @@ -71,6 +73,7 @@

WSGI_APPLICATION = 'demo.wsgi.application'

ASGI_APPLICATION = 'demo.routing.application'

# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases
Expand Down Expand Up @@ -127,6 +130,10 @@

import dash_core_components as dcc
_rname = os.path.join(os.path.dirname(dcc.__file__),'..')
for dash_module_name in ['dash_core_components','dash_html_components','dash_renderer',]:
for dash_module_name in ['dash_core_components',
'dash_html_components',
'dash_renderer',]:
STATICFILES_DIRS.append( ("dash/%s"%dash_module_name, os.path.join(_rname,dash_module_name)) )

# Fudge to work with channels in debug mode
STATICFILES_DIRS.append(("dash/dpd_components","/home/mark/local/dpd-components/lib"))
23 changes: 20 additions & 3 deletions demo/demo/templates/index.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
<!DOCTYPE HTML>
<html>
{%load plotly_dash%}
<title>Simple stuff</title>
<head>
{%load plotly_dash%}
<title>Simple stuff</title>
</head>
<body>
<div>
<p>Navigational links :
<a href="{%url "home"%}">Main Page</a>
<a href="{%url "second"%}">Second Page</a>
</p>
</div>
<div>
Content here
{%plotly_app slug="simpleexample-1" ratio=0.2 %}
Expand All @@ -15,5 +23,14 @@
Content here
{%plotly_app name="Ex2"%}
</div>
</body>
<div>
WS Content here
{%plotly_app name="Connected"%}
</div>
<div>
WS Content here
{%plotly_app slug="connected-2"%}
</div>
</body>
{%plotly_message_pipe%}
</html>
23 changes: 23 additions & 0 deletions demo/demo/templates/second_page.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!DOCTYPE HTML>
<html>
<head>
{%load plotly_dash%}
<title>More Examples</title>
</head>
<body>
<div>
<p>Navigational links :
<a href="{%url "home"%}">Main Page</a>
<a href="{%url "second"%}">Second Page</a>
</p>
</div>
<div>
WS Content here
{%plotly_app name="Connected"%}
</div>
<div>
WS Content here
{%plotly_app slug="connected-2"%}
</div>
</body>
</html>
3 changes: 2 additions & 1 deletion demo/demo/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
import demo.plotly_apps

urlpatterns = [
path('', TemplateView.as_view(template_name='index.html')),
path('', TemplateView.as_view(template_name='index.html'), name="home"),
path('second_page', TemplateView.as_view(template_name='second_page.html'), name="second"),
path('admin/', admin.site.urls),
path('django_plotly_dash/', include('django_plotly_dash.urls')),
]
13 changes: 13 additions & 0 deletions dev_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
alabaster==0.7.10
argh==0.26.2
asgiref==2.3.2
async-timeout==2.0.1
attrs==18.1.0
autobahn==18.6.1
Automat==0.6.0
Babel==2.5.3
certifi==2018.4.16
channels==2.1.2
chardet==3.0.4
click==6.7
constantly==15.1.0
daphne==2.2.0
dash==0.21.1
dash-core-components==0.22.1
dash-html-components==0.10.1
Expand All @@ -15,8 +23,10 @@ docutils==0.14
Flask==1.0.2
Flask-Compress==1.4.0
grip==4.5.2
hyperlink==18.0.0
idna==2.6
imagesize==1.0.0
incremental==17.5.0
ipython-genutils==0.2.0
itsdangerous==0.24
Jinja2==2.10
Expand Down Expand Up @@ -48,6 +58,9 @@ tornado==5.0.2
tqdm==4.23.3
traitlets==4.3.2
twine==1.11.0
Twisted==18.4.0
txaio==2.10.0
urllib3==1.22
watchdog==0.8.3
Werkzeug==0.14.1
zope.interface==4.5.0
2 changes: 1 addition & 1 deletion django_plotly_dash/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#

__version__ = "0.3.0"
__version__ = "0.4.0"

from .dash_wrapper import DjangoDash

2 changes: 1 addition & 1 deletion django_plotly_dash/dash_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def __init__(self, base_pathname=None, replacements = None, ndid=None, expanded_
super(WrappedDash, self).__init__(**kwargs)

self.css.config.serve_locally = True
self.css.config.serve_locally = False
#self.css.config.serve_locally = False

self.scripts.config.serve_locally = self.css.config.serve_locally

Expand Down
24 changes: 24 additions & 0 deletions django_plotly_dash/migrations/0002_add_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,33 @@ def addExamples(apps, schema_editor):

da1.save()

sa2 = StatelessApp(app_name="Connected",
slug="connected")

sa2.save()

da2 = DashApp(stateless_app=sa2,
instance_name="Connected-2",
slug="connected-2",
base_state='''{"dynamic": {"value": "Dynamo 123",
"uid": "need_to_generate_this",
"channel_name": "test_widget_channel",
"label": "rotational energy"},
"dropdown-one": {"value": "Oxygen"},
"also_dynamic": {"value": "Alternator 456",
"uid": "and_this_one",
"channel_name": "test_widget_channel",
"label": "momentum"}}''',
save_on_change=True)

da2.save()


def remExamples(apps, schema_editor):

DashApp = apps.get_model("django_plotly_dash","DashApp")
StatelessApp = apps.get_model("django_plotly_dash","StatelessApp")

DashApp.objects.all().delete()
StatelessApp.objects.all().delete()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{%load staticfiles%}
<script src="{%static "channels/js/websocketbridge.js"%}"></script>
<script>
if( !window.dpd_wsb ) {
const wsb = new channels.WebSocketBridge();
wsb.connect("{{url}}");
var dpd_wsb = {
wsb: wsb,
callbacks: [],
add_callback: function(cb) { this.callbacks.push(cb); },
send:function(message) { this.wsb.send(message); }
};
wsb.listen(function(action, stream) {
for(var i in dpd_wsb.callbacks) {
dpd_wsb.callbacks[i](action, stream);
}
});
window.dpd_wsb = dpd_wsb;
if( window.dpd_wsb_pre ) {
for(var i in window.dpd_wsb_pre.callbacks ) dpd_wsb.add_callback(window.dpd_wsb_pre.callbacks[i]);
window.dpd_wsb_pre.callbacks = [];
for(var j in window.dpd_wsb_pre.sender_targets ) window.dpd_wsb_pre.sender_targets[i].add_sender(dpd_wsb);
window.dpd_wsb_pre.sender_targets = [];
}
}
</script>
4 changes: 4 additions & 0 deletions django_plotly_dash/templatetags/plotly_dash.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,7 @@ def plotly_app(context, name=None, slug=None, da=None, ratio=0.1, use_frameborde

return locals()

@register.inclusion_tag("django_plotly_dash/plotly_messaging.html", takes_context=True)
def plotly_message_pipe(context, url=None):
url = url and url or '/ws/channel'
return locals()
5 changes: 4 additions & 1 deletion docs/extended_callbacks.rst
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,8 @@ in the :ref:`models_and_state` section.
Using session state
------------------

Changes to the session state and other server-side objects are not automatically propagated to an application. Something in the front-end UI has to invoke a callaback; at this point the latest version of these objects will be provided to the callback. The same considerations as in other Dash `live updates <https://dash.plot.ly/live-updates>`_ apply.
Changes to the session state and other server-side objects are not automatically
propagated to an application. Something in the front-end UI has to invoke a callback; at this point the
latest version of these objects will be provided to the callback. The same considerations
as in other Dash `live updates <https://dash.plot.ly/live-updates>`_ apply.

27 changes: 24 additions & 3 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,35 @@ Use pip to install the package, preferably to a local virtualenv.::

pip install django_plotly_dash

Then, add ``django_plotly_dash`` to ``INSTALLED_APPS`` in the Django settings.py file::
Then, add ``django_plotly_dash`` to ``INSTALLED_APPS`` in the Django ``settings.py`` file::

INSTALLED_APPS = [
...
'django_plotly_dash.apps.DjangoPlotlyDashConfig',
...
]

The project directory name ``django_plotly_dash`` can also be used on its own if preferred, but this will stop the use of readable application names in
the Django admin interface.

The application's routes need to be registered within the routing structure by an appropriate ``include`` statement in
a ``urls.py`` file::

urlpatterns = [
...
path('django_plotly_dash/', include('django_plotly_dash.urls')),
]

The name within the URL is not important and can be changed.

For the final installation step, a migration is needed to update the
database::

./manage.py migrate

The ``plotly_item`` tag in the ``plotly_dash`` tag library can then be used to render any registered dash component. See :ref:`simple_use` for a simple example.

The project directory name ``django_plotly_dash`` can also be used on its own if preferred, but this will then skip the use of readable application names in
the Django admin interface.
It is important to ensure that any applications are registered using the ``DjangoDash`` class. This means that any python module containing the registration code has to be known to Django and loaded at the appropriate time. An easy way to ensure this is to import these modules into a standard Django file loaded at registration time.

Source code and demo
--------------------
Expand All @@ -40,3 +57,7 @@ To install and run it::
# at http://localhost:8000

This will launch a simple Django application. A superuser account is also configured, with both username and password set to ``admin``.

Note that the current demo, along with the codebase, is in a prerelease and very raw form.


0 comments on commit bcafcd4

Please sign in to comment.