Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 11 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
# django-plotly-dash

Expose [plotly dash](https://plot.ly/products/dash/) apps as django tags.
Expose [plotly dash](https://plot.ly/products/dash/) apps as [Django](https:://www.djangoproject.com/) tags. Multiple Dash apps can
then be embedded into a single web page, persist and share internal state, and also have access to the
current user and session variables.

See the source for this project here:
<https://github.com/GibbsConsulting/django-plotly-dash>

This README file provides a short guide to installing and using the package, and also
outlines how to run the demonstration application.



More detailed information
can be found in the online documentation at
<https://readthedocs.org/projects/django-plotly-dash>
Expand Down Expand Up @@ -45,8 +49,8 @@ cd django-plotly-dash

## Usage

To use existing dash applications, first register them using the `DelayedDash` class. This
replaces the `dash.Dash` class of `plotly.py.`
To use existing dash applications, first register them using the `DjangoDash` class. This
replaces the `Dash` class of the `dash` package.

Taking a very simple example inspired by the excellent [getting started](https://dash.plot.ly/) documentation:

Expand All @@ -55,9 +59,9 @@ import dash
import dash_core_components as dcc
import dash_html_components as html

from django_plotly_dash import DelayedDash
from django_plotly_dash import DjangoDash

app = DelayedDash('SimpleExample')
app = DjangoDash('SimpleExample')

app.layout = html.Div([
dcc.RadioItems(
Expand Down Expand Up @@ -88,30 +92,15 @@ def callback_color(dropdown_value):
def callback_size(dropdown_color, dropdown_size):
return "The chosen T-shirt is a %s %s one." %(dropdown_size,
dropdown_color)

a2 = DelayedDash("Ex2")
a2.layout = html.Div([
dcc.RadioItems(id="dropdown-one",options=[{'label':i,'value':j} for i,j in [
("O2","Oxygen"),("N2","Nitrogen"),]
],value="Oxygen"),
html.Div(id="output-one")
])

@a2.callback(
dash.dependencies.Output('output-one','children'),
[dash.dependencies.Input('dropdown-one','value')]
)
def callback_c(*args,**kwargs):
return "Args are %s and kwargs are %s" %("".join(*args),str(kwargs))
```

Note that the `DelayedDash` constructor requires a name to be specified. This name is then used to identify the dash app in
Note that the `DjangoDash` constructor requires a name to be specified. This name is then used to identify the dash app in
templates:

```jinja2
{% load plotly_dash %}

{% plotly_item "SimpleExample" %}
{% plotly_item name="SimpleExample" %}
```

The registration code needs to be in a location
Expand Down
11 changes: 6 additions & 5 deletions demo/demo/plotly_apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import dash_core_components as dcc
import dash_html_components as html

from django_plotly_dash import DelayedDash
from django_plotly_dash import DjangoDash

app = DelayedDash('SimpleExample')
app = DjangoDash('SimpleExample')

app.layout = html.Div([
dcc.RadioItems(
Expand Down Expand Up @@ -36,10 +36,10 @@ def callback_size(dropdown_color, dropdown_size):
return "The chosen T-shirt is a %s %s one." %(dropdown_size,
dropdown_color)

a2 = DelayedDash("Ex2")
a2 = DjangoDash("Ex2")
a2.layout = html.Div([
dcc.RadioItems(id="dropdown-one",options=[{'label':i,'value':j} for i,j in [
("O2","Oxygen"),("N2","Nitrogen"),]
("O2","Oxygen"),("N2","Nitrogen"),("CO2","Carbon Dioxide")]
],value="Oxygen"),
html.Div(id="output-one")
])
Expand All @@ -49,5 +49,6 @@ def callback_size(dropdown_color, dropdown_size):
[dash.dependencies.Input('dropdown-one','value')]
)
def callback_c(*args,**kwargs):
return "Args are %s and kwargs are %s" %("".join(*args),str(kwargs))
da = kwargs['dash_app']
return "Args are [%s] and kwargs are %s" %(",".join(args),str(kwargs))

2 changes: 1 addition & 1 deletion demo/demo/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'
TIME_ZONE = 'America/Vancouver'

USE_I18N = True

Expand Down
6 changes: 3 additions & 3 deletions demo/demo/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
<body>
<div>
Content here
{%plotly_item "simpleexample-1" ratio=0.2 %}
{%plotly_app slug="simpleexample-1" ratio=0.2 %}
</div>
<div>
Content here
{%plotly_item "SimpleExample"%}
{%plotly_app name="SimpleExample"%}
</div>
<div>
Content here
{%plotly_item "Ex2"%}
{%plotly_app name="Ex2"%}
</div>
</body>
</html>
4 changes: 2 additions & 2 deletions django_plotly_dash/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#

__version__ = "0.0.4"
__version__ = "0.1.0"

from .dash_wrapper import DelayedDash
from .dash_wrapper import DjangoDash

100 changes: 52 additions & 48 deletions django_plotly_dash/dash_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from django.urls import reverse
from django.http import HttpResponse
from django.utils.text import slugify

import json

Expand All @@ -13,43 +14,20 @@
uid_counter = 0

usable_apps = {}
nd_apps = {}

def add_usable_app(name, app):
name = slugify(name)
global usable_apps
usable_apps[name] = app
return name

def add_instance(id, instance):
global nd_apps
nd_apps[id] = instance

def get_app_by_name(name):
'''
Locate a registered dash app by name, and return a DelayedDash instance encapsulating the app.
'''
return usable_apps.get(name,None)

def get_app_instance_by_id(id):
def get_stateless_by_name(name):
'''
Locate an instance of a dash app by identifier, or return None if one does not exist
Locate a registered dash app by name, and return a DjangoDash instance encapsulating the app.
'''
return nd_apps.get(id,None)

def clear_app_instance(id):
try:
del nd_apps[id]
except:
pass

def get_or_form_app(id, name, **kwargs):
'''
Locate an instance of a dash app by identifier, loading or creating a new instance if needed
'''
app = get_app_instance_by_id(id)
if app:
return app
dd = get_app_by_name(name)
return dd.form_dash_instance()
name = slugify(name)
# TODO wrap this in raising a 404 if not found
return usable_apps[name]

class Holder:
def __init__(self):
Expand All @@ -59,7 +37,7 @@ def append_css(self, stylesheet):
def append_script(self, script):
self.items.append(script)

class DelayedDash:
class DjangoDash:
def __init__(self, name=None, **kwargs):
if name is None:
global uid_counter
Expand All @@ -78,9 +56,30 @@ def __init__(self, name=None, **kwargs):

self._expanded_callbacks = False

def as_dash_instance(self):
'''
Form a dash instance, for stateless use of this app
'''
return self.form_dash_instance()

def handle_current_state(self):
'Do nothing impl - only matters if state present'
pass
def update_current_state(self, wid, key, value):
'Do nothing impl - only matters if state present'
pass
def have_current_state_entry(self, wid, key):
'Do nothing impl - only matters if state present'
pass

def form_dash_instance(self, replacements=None, specific_identifier=None):
if not specific_identifier:
app_pathname = "%s:app-%s"% (app_name, main_view_label)
else:
app_pathname="%s:%s" % (app_name, main_view_label)

rd = NotDash(name_root=self._uid,
app_pathname="%s:%s" % (app_name, main_view_label),
app_pathname=app_pathname,
expanded_callbacks = self._expanded_callbacks,
replacements = replacements,
specific_identifier = specific_identifier)
Expand Down Expand Up @@ -134,8 +133,6 @@ def __init__(self, name_root, app_pathname=None, replacements = None, specific_i
else:
self._uid = name_root

add_instance(self._uid, self)

self._flask_app = Flask(self._uid)
self._notflask = NotFlask()
self._base_pathname = reverse(app_pathname,kwargs={'id':self._uid})
Expand Down Expand Up @@ -279,20 +276,27 @@ def dispatch_with_args(self, body, argMap):

target_id = '{}.{}'.format(output['id'], output['property'])
args = []
for component_registration in self.callback_map[target_id]['inputs']:
args.append([
c.get('value', None) for c in inputs if
c['property'] == component_registration['property'] and
c['id'] == component_registration['id']
][0])

for component_registration in self.callback_map[target_id]['state']:
args.append([
c.get('value', None) for c in state if
c['property'] == component_registration['property'] and
c['id'] == component_registration['id']
][0])

return self.callback_map[target_id]['callback'](*args,**argMap)
da = argMap.get('dash_app', None)

for component_registration in self.callback_map[target_id]['inputs']:
for c in inputs:
if c['property'] == component_registration['property'] and c['id'] == component_registration['id']:
v = c.get('value',None)
args.append(v)
if da: da.update_current_state(c['id'],c['property'],v)

for component_registration in self.callback_map[target_id]['state']:
for c in state:
if c['property'] == component_registration['property'] and c['id'] == component_registration['id']:
v = c.get('value',None)
args.append(v)
if da: da.update_current_state(c['id'],c['property'],v)

res = self.callback_map[target_id]['callback'](*args,**argMap)
if da and da.have_current_state_entry(output['id'], output['property']):
response = json.loads(res.data.decode('utf-8'))
value = response.get('response',{}).get('props',{}).get(output['property'],None)
da.update_current_state(output['id'], output['property'], value)

return res
23 changes: 23 additions & 0 deletions django_plotly_dash/migrations/0003_auto_20180514_1802.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 2.0.5 on 2018-05-15 01:02

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('django_plotly_dash', '0002_simple_example_state'),
]

operations = [
migrations.AddField(
model_name='dashapp',
name='save_on_change',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='dashapp',
name='base_state',
field=models.TextField(default='{}'),
),
]
Loading