Skip to content

Commit 5c18cfb

Browse files
delsimGibbsConsulting
authored andcommitted
Version 0.1 (#6)
* Refactored app loading and refreshed documentation * Neater object location * Expand range of valid input for template tag * Use slugified form of all app names * Added session state to main callback * Fix timezones and object extraction when initially populating * Added a save-on-change flag to allow the automatic updating of objects in the database. Centralised the calls so caching can be used as well (or instead) if desired * Refactored DelayedDash into DjangoDash class. Extended documentation scope * Documentation on extended callbacks and an overview of how things work * Brief documentation on the template tag * Add example use of template tag * Added model documentation * Initial release of v0.1
1 parent ba772a4 commit 5c18cfb

File tree

17 files changed

+378
-143
lines changed

17 files changed

+378
-143
lines changed

README.md

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
# django-plotly-dash
22

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

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

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

13+
14+
1115
More detailed information
1216
can be found in the online documentation at
1317
<https://readthedocs.org/projects/django-plotly-dash>
@@ -45,8 +49,8 @@ cd django-plotly-dash
4549

4650
## Usage
4751

48-
To use existing dash applications, first register them using the `DelayedDash` class. This
49-
replaces the `dash.Dash` class of `plotly.py.`
52+
To use existing dash applications, first register them using the `DjangoDash` class. This
53+
replaces the `Dash` class of the `dash` package.
5054

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

@@ -55,9 +59,9 @@ import dash
5559
import dash_core_components as dcc
5660
import dash_html_components as html
5761

58-
from django_plotly_dash import DelayedDash
62+
from django_plotly_dash import DjangoDash
5963

60-
app = DelayedDash('SimpleExample')
64+
app = DjangoDash('SimpleExample')
6165

6266
app.layout = html.Div([
6367
dcc.RadioItems(
@@ -88,30 +92,15 @@ def callback_color(dropdown_value):
8892
def callback_size(dropdown_color, dropdown_size):
8993
return "The chosen T-shirt is a %s %s one." %(dropdown_size,
9094
dropdown_color)
91-
92-
a2 = DelayedDash("Ex2")
93-
a2.layout = html.Div([
94-
dcc.RadioItems(id="dropdown-one",options=[{'label':i,'value':j} for i,j in [
95-
("O2","Oxygen"),("N2","Nitrogen"),]
96-
],value="Oxygen"),
97-
html.Div(id="output-one")
98-
])
99-
100-
@a2.callback(
101-
dash.dependencies.Output('output-one','children'),
102-
[dash.dependencies.Input('dropdown-one','value')]
103-
)
104-
def callback_c(*args,**kwargs):
105-
return "Args are %s and kwargs are %s" %("".join(*args),str(kwargs))
10695
```
10796

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

111100
```jinja2
112101
{% load plotly_dash %}
113102
114-
{% plotly_item "SimpleExample" %}
103+
{% plotly_item name="SimpleExample" %}
115104
```
116105

117106
The registration code needs to be in a location

demo/demo/plotly_apps.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
import dash_core_components as dcc
33
import dash_html_components as html
44

5-
from django_plotly_dash import DelayedDash
5+
from django_plotly_dash import DjangoDash
66

7-
app = DelayedDash('SimpleExample')
7+
app = DjangoDash('SimpleExample')
88

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

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

demo/demo/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@
107107

108108
LANGUAGE_CODE = 'en-us'
109109

110-
TIME_ZONE = 'UTC'
110+
TIME_ZONE = 'America/Vancouver'
111111

112112
USE_I18N = True
113113

demo/demo/templates/index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
<body>
66
<div>
77
Content here
8-
{%plotly_item "simpleexample-1" ratio=0.2 %}
8+
{%plotly_app slug="simpleexample-1" ratio=0.2 %}
99
</div>
1010
<div>
1111
Content here
12-
{%plotly_item "SimpleExample"%}
12+
{%plotly_app name="SimpleExample"%}
1313
</div>
1414
<div>
1515
Content here
16-
{%plotly_item "Ex2"%}
16+
{%plotly_app name="Ex2"%}
1717
</div>
1818
</body>
1919
</html>

django_plotly_dash/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#
22

3-
__version__ = "0.0.4"
3+
__version__ = "0.1.0"
44

5-
from .dash_wrapper import DelayedDash
5+
from .dash_wrapper import DjangoDash
66

django_plotly_dash/dash_wrapper.py

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from django.urls import reverse
55
from django.http import HttpResponse
6+
from django.utils.text import slugify
67

78
import json
89

@@ -13,43 +14,20 @@
1314
uid_counter = 0
1415

1516
usable_apps = {}
16-
nd_apps = {}
1717

1818
def add_usable_app(name, app):
19+
name = slugify(name)
1920
global usable_apps
2021
usable_apps[name] = app
22+
return name
2123

22-
def add_instance(id, instance):
23-
global nd_apps
24-
nd_apps[id] = instance
25-
26-
def get_app_by_name(name):
27-
'''
28-
Locate a registered dash app by name, and return a DelayedDash instance encapsulating the app.
29-
'''
30-
return usable_apps.get(name,None)
31-
32-
def get_app_instance_by_id(id):
24+
def get_stateless_by_name(name):
3325
'''
34-
Locate an instance of a dash app by identifier, or return None if one does not exist
26+
Locate a registered dash app by name, and return a DjangoDash instance encapsulating the app.
3527
'''
36-
return nd_apps.get(id,None)
37-
38-
def clear_app_instance(id):
39-
try:
40-
del nd_apps[id]
41-
except:
42-
pass
43-
44-
def get_or_form_app(id, name, **kwargs):
45-
'''
46-
Locate an instance of a dash app by identifier, loading or creating a new instance if needed
47-
'''
48-
app = get_app_instance_by_id(id)
49-
if app:
50-
return app
51-
dd = get_app_by_name(name)
52-
return dd.form_dash_instance()
28+
name = slugify(name)
29+
# TODO wrap this in raising a 404 if not found
30+
return usable_apps[name]
5331

5432
class Holder:
5533
def __init__(self):
@@ -59,7 +37,7 @@ def append_css(self, stylesheet):
5937
def append_script(self, script):
6038
self.items.append(script)
6139

62-
class DelayedDash:
40+
class DjangoDash:
6341
def __init__(self, name=None, **kwargs):
6442
if name is None:
6543
global uid_counter
@@ -78,9 +56,30 @@ def __init__(self, name=None, **kwargs):
7856

7957
self._expanded_callbacks = False
8058

59+
def as_dash_instance(self):
60+
'''
61+
Form a dash instance, for stateless use of this app
62+
'''
63+
return self.form_dash_instance()
64+
65+
def handle_current_state(self):
66+
'Do nothing impl - only matters if state present'
67+
pass
68+
def update_current_state(self, wid, key, value):
69+
'Do nothing impl - only matters if state present'
70+
pass
71+
def have_current_state_entry(self, wid, key):
72+
'Do nothing impl - only matters if state present'
73+
pass
74+
8175
def form_dash_instance(self, replacements=None, specific_identifier=None):
76+
if not specific_identifier:
77+
app_pathname = "%s:app-%s"% (app_name, main_view_label)
78+
else:
79+
app_pathname="%s:%s" % (app_name, main_view_label)
80+
8281
rd = NotDash(name_root=self._uid,
83-
app_pathname="%s:%s" % (app_name, main_view_label),
82+
app_pathname=app_pathname,
8483
expanded_callbacks = self._expanded_callbacks,
8584
replacements = replacements,
8685
specific_identifier = specific_identifier)
@@ -134,8 +133,6 @@ def __init__(self, name_root, app_pathname=None, replacements = None, specific_i
134133
else:
135134
self._uid = name_root
136135

137-
add_instance(self._uid, self)
138-
139136
self._flask_app = Flask(self._uid)
140137
self._notflask = NotFlask()
141138
self._base_pathname = reverse(app_pathname,kwargs={'id':self._uid})
@@ -279,20 +276,27 @@ def dispatch_with_args(self, body, argMap):
279276

280277
target_id = '{}.{}'.format(output['id'], output['property'])
281278
args = []
282-
for component_registration in self.callback_map[target_id]['inputs']:
283-
args.append([
284-
c.get('value', None) for c in inputs if
285-
c['property'] == component_registration['property'] and
286-
c['id'] == component_registration['id']
287-
][0])
288-
289-
for component_registration in self.callback_map[target_id]['state']:
290-
args.append([
291-
c.get('value', None) for c in state if
292-
c['property'] == component_registration['property'] and
293-
c['id'] == component_registration['id']
294-
][0])
295279

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

282+
for component_registration in self.callback_map[target_id]['inputs']:
283+
for c in inputs:
284+
if c['property'] == component_registration['property'] and c['id'] == component_registration['id']:
285+
v = c.get('value',None)
286+
args.append(v)
287+
if da: da.update_current_state(c['id'],c['property'],v)
298288

289+
for component_registration in self.callback_map[target_id]['state']:
290+
for c in state:
291+
if c['property'] == component_registration['property'] and c['id'] == component_registration['id']:
292+
v = c.get('value',None)
293+
args.append(v)
294+
if da: da.update_current_state(c['id'],c['property'],v)
295+
296+
res = self.callback_map[target_id]['callback'](*args,**argMap)
297+
if da and da.have_current_state_entry(output['id'], output['property']):
298+
response = json.loads(res.data.decode('utf-8'))
299+
value = response.get('response',{}).get('props',{}).get(output['property'],None)
300+
da.update_current_state(output['id'], output['property'], value)
301+
302+
return res
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Generated by Django 2.0.5 on 2018-05-15 01:02
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('django_plotly_dash', '0002_simple_example_state'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='dashapp',
15+
name='save_on_change',
16+
field=models.BooleanField(default=False),
17+
),
18+
migrations.AlterField(
19+
model_name='dashapp',
20+
name='base_state',
21+
field=models.TextField(default='{}'),
22+
),
23+
]

0 commit comments

Comments
 (0)