Skip to content

Commit 578dcfe

Browse files
Merge pull request #8 from delsim/master
Move to 0.3.0
2 parents 64448f0 + 53903b7 commit 578dcfe

File tree

12 files changed

+204
-107
lines changed

12 files changed

+204
-107
lines changed

django_plotly_dash/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#
22

3-
__version__ = "0.2.0"
3+
__version__ = "0.3.0"
44

55
from .dash_wrapper import DjangoDash
66

django_plotly_dash/admin.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
from django.contrib import admin
22

3-
from .models import DashApp, DashAppAdmin
3+
from .models import (DashApp, DashAppAdmin,
4+
StatelessApp, StatelessAppAdmin,
5+
)
46

57
admin.site.register(DashApp, DashAppAdmin)
8+
admin.site.register(StatelessApp, StatelessAppAdmin)
69

django_plotly_dash/dash_wrapper.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def add_usable_app(name, app):
2121
usable_apps[name] = app
2222
return name
2323

24-
def get_stateless_by_name(name):
24+
def get_local_stateless_by_name(name):
2525
'''
2626
Locate a registered dash app by name, and return a DjangoDash instance encapsulating the app.
2727
'''
@@ -60,7 +60,7 @@ def as_dash_instance(self):
6060
'''
6161
Form a dash instance, for stateless use of this app
6262
'''
63-
return self.form_dash_instance()
63+
return self.do_form_dash_instance()
6464

6565
def handle_current_state(self):
6666
'Do nothing impl - only matters if state present'
@@ -80,21 +80,23 @@ def get_base_pathname(self, specific_identifier):
8080
app_pathname="%s:%s" % (app_name, main_view_label)
8181
ndid = specific_identifier
8282

83-
try:
84-
full_url = reverse(app_pathname,kwargs={'id':ndid})
85-
except:
86-
full_url = "/%s/" %ndid
87-
83+
full_url = reverse(app_pathname,kwargs={'id':ndid})
8884
return ndid, full_url
8985

90-
def form_dash_instance(self, replacements=None, specific_identifier=None):
86+
def do_form_dash_instance(self, replacements=None, specific_identifier=None):
9187

9288
ndid, base_pathname = self.get_base_pathname(specific_identifier)
89+
return self.form_dash_instance(replacements, ndid, base_pathname)
90+
91+
def form_dash_instance(self, replacements=None, ndid=None, base_pathname=None):
92+
93+
if ndid is None:
94+
ndid = self._uid
9395

94-
rd = NotDash(base_pathname=base_pathname,
95-
expanded_callbacks = self._expanded_callbacks,
96-
replacements = replacements,
97-
ndid = ndid)
96+
rd = WrappedDash(base_pathname=base_pathname,
97+
expanded_callbacks = self._expanded_callbacks,
98+
replacements = replacements,
99+
ndid = ndid)
98100

99101
rd.layout = self.layout
100102

@@ -121,7 +123,7 @@ def expanded_callback(self, output, inputs=[], state=[], events=[]):
121123
self._expanded_callbacks = True
122124
return self.callback(output, inputs, state, events)
123125

124-
class NotFlask:
126+
class PseudoFlask:
125127
def __init__(self):
126128
self.config = {}
127129
self.endpoints = {}
@@ -138,22 +140,22 @@ def before_first_request(self,*args,**kwargs):
138140
def run(self,*args,**kwargs):
139141
pass
140142

141-
class NotDash(Dash):
143+
class WrappedDash(Dash):
142144
def __init__(self, base_pathname=None, replacements = None, ndid=None, expanded_callbacks=False, **kwargs):
143145

144146
self._uid = ndid
145147

146148
self._flask_app = Flask(self._uid)
147-
self._notflask = NotFlask()
149+
self._notflask = PseudoFlask()
148150
self._base_pathname = base_pathname
149151

150152
kwargs['url_base_pathname'] = self._base_pathname
151153
kwargs['server'] = self._notflask
152154

153-
super(NotDash, self).__init__(**kwargs)
155+
super(WrappedDash, self).__init__(**kwargs)
154156

155157
self.css.config.serve_locally = True
156-
#self.css.config.serve_locally = False
158+
self.css.config.serve_locally = False
157159

158160
self.scripts.config.serve_locally = self.css.config.serve_locally
159161

@@ -273,10 +275,10 @@ def _fix_callback_item(self, item):
273275
return item
274276

275277
def callback(self, output, inputs=[], state=[], events=[]):
276-
return super(NotDash, self).callback(self._fix_callback_item(output),
277-
[self._fix_callback_item(x) for x in inputs],
278-
[self._fix_callback_item(x) for x in state],
279-
[self._fix_callback_item(x) for x in events])
278+
return super(WrappedDash, self).callback(self._fix_callback_item(output),
279+
[self._fix_callback_item(x) for x in inputs],
280+
[self._fix_callback_item(x) for x in state],
281+
[self._fix_callback_item(x) for x in events])
280282

281283
def dispatch(self):
282284
import flask

django_plotly_dash/migrations/0001_initial.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
# Generated by Django 2.0.5 on 2018-05-10 21:48
1+
# Generated by Django 2.0.5 on 2018-06-08 20:02
22

33
from django.db import migrations, models
4+
import django.db.models.deletion
45

56

67
class Migration(migrations.Migration):
@@ -15,12 +16,25 @@ class Migration(migrations.Migration):
1516
name='DashApp',
1617
fields=[
1718
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18-
('app_name', models.CharField(max_length=100)),
1919
('instance_name', models.CharField(blank=True, max_length=100, unique=True)),
2020
('slug', models.SlugField(blank=True, max_length=110, unique=True)),
21-
('base_state', models.TextField()),
21+
('base_state', models.TextField(default='{}')),
2222
('creation', models.DateTimeField(auto_now_add=True)),
2323
('update', models.DateTimeField(auto_now=True)),
24+
('save_on_change', models.BooleanField(default=False)),
2425
],
2526
),
27+
migrations.CreateModel(
28+
name='StatelessApp',
29+
fields=[
30+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
31+
('app_name', models.CharField(max_length=100, unique=True)),
32+
('slug', models.SlugField(blank=True, max_length=110, unique=True)),
33+
],
34+
),
35+
migrations.AddField(
36+
model_name='dashapp',
37+
name='stateless_app',
38+
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='django_plotly_dash.StatelessApp'),
39+
),
2640
]
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Generated by Django 2.0.5 on 2018-06-08 20:03
2+
3+
from django.db import migrations
4+
5+
def addExamples(apps, schema_editor):
6+
7+
DashApp = apps.get_model("django_plotly_dash","DashApp")
8+
StatelessApp = apps.get_model("django_plotly_dash","StatelessApp")
9+
10+
sa1 = StatelessApp(app_name="SimpleExample",
11+
slug="simple-example")
12+
13+
sa1.save()
14+
15+
da1 = DashApp(stateless_app=sa1,
16+
instance_name="SimpleExample-1",
17+
slug="simpleexample-1",
18+
base_state='{"dropdown-color":{"value":"blue"},"dropdown-size":{"value":"small"}}')
19+
20+
da1.save()
21+
22+
23+
def remExamples(apps, schema_editor):
24+
25+
DashApp.objects.all().delete()
26+
StatelessApp.objects.all().delete()
27+
28+
class Migration(migrations.Migration):
29+
30+
dependencies = [
31+
('django_plotly_dash', '0001_initial'),
32+
]
33+
34+
operations = [
35+
migrations.RunPython(addExamples, remExamples),
36+
]

django_plotly_dash/migrations/0002_simple_example_state.py

Lines changed: 0 additions & 28 deletions
This file was deleted.

django_plotly_dash/migrations/0003_auto_20180514_1802.py

Lines changed: 0 additions & 23 deletions
This file was deleted.

django_plotly_dash/models.py

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,59 @@
33
from django.utils.text import slugify
44
from django.shortcuts import get_object_or_404
55

6-
from .dash_wrapper import get_stateless_by_name
6+
from .dash_wrapper import get_local_stateless_by_name
77

88
import json
99

10+
def get_stateless_by_name(name):
11+
return get_local_stateless_by_name(name)
12+
13+
class StatelessApp(models.Model):
14+
'''
15+
A stateless Dash app. An instance of this model represents a dash app without any specific state
16+
'''
17+
app_name = models.CharField(max_length=100, blank=False, null=False, unique=True)
18+
slug = models.SlugField(max_length=110, unique=True, blank=True)
19+
20+
def __str__(self):
21+
return self.app_name
22+
23+
def save(self, *args, **kwargs):
24+
if not self.slug or len(self.slug) < 2:
25+
self.slug = slugify(self.app_name)
26+
return super(StatelessApp, self).save(*args,**kwargs)
27+
28+
def as_dash_app(self):
29+
'''
30+
Return a DjangoDash instance of the dash application
31+
'''
32+
dd = getattr(self,'_stateless_dash_app_instance',None)
33+
if not dd:
34+
dd = get_stateless_by_name(self.app_name)
35+
setattr(self,'_stateless_dash_app_instance',dd)
36+
return dd
37+
38+
def find_stateless_by_name(name):
39+
try:
40+
dsa = StatelessApp.objects.get(app_name=name)
41+
return dsa.as_dash_app()
42+
except:
43+
pass
44+
45+
da = get_stateless_by_name(name)
46+
dsa = StatelessApp(app_name=name)
47+
dsa.save()
48+
return da
49+
50+
class StatelessAppAdmin(admin.ModelAdmin):
51+
list_display = ['app_name','slug',]
52+
list_filter = ['app_name','slug',]
53+
1054
class DashApp(models.Model):
1155
'''
1256
An instance of this model represents a dash application and its internal state
1357
'''
14-
app_name = models.CharField(max_length=100, blank=False, null=False, unique=False)
58+
stateless_app = models.ForeignKey(StatelessApp, on_delete=models.PROTECT, unique=False, null=False, blank=False)
1559
instance_name = models.CharField(max_length=100, unique=True, blank=True, null=False)
1660
slug = models.SlugField(max_length=110, unique=True, blank=True)
1761
base_state = models.TextField(null=False, default="{}") # If mandating postgresql then this could be a JSONField
@@ -24,19 +68,12 @@ def __str__(self):
2468

2569
def save(self, *args, **kwargs):
2670
if not self.instance_name:
27-
existing_count = DashApp.objects.filter(app_name=self.app_name).count()
28-
self.instance_name = "%s-%i" %(self.app_name, existing_count+1)
71+
existing_count = DashApp.objects.all().count()
72+
self.instance_name = "%s-%i" %(self.stateless_app.app_name, existing_count+1)
2973
if not self.slug or len(self.slug) < 2:
3074
self.slug = slugify(self.instance_name)
3175
super(DashApp, self).save(*args,**kwargs)
3276

33-
def _stateless_dash_app(self):
34-
dd = getattr(self,'_stateless_dash_app_instance',None)
35-
if not dd:
36-
dd = get_stateless_by_name(self.app_name)
37-
setattr(self,'_stateless_dash_app_instance',dd)
38-
return dd
39-
4077
def handle_current_state(self):
4178
'''
4279
Check to see if the current hydrated state and the saved state are different.
@@ -82,16 +119,16 @@ def current_state(self):
82119
return cs
83120

84121
def as_dash_instance(self):
85-
dd = self._stateless_dash_app()
122+
dd = self.stateless_app.as_dash_app()
86123
base = self.current_state()
87-
return dd.form_dash_instance(replacements=base,
88-
specific_identifier=self.slug)
124+
return dd.do_form_dash_instance(replacements=base,
125+
specific_identifier=self.slug)
89126

90127
def _get_base_state(self):
91128
'''
92129
Get the base state of the object, as defined by the app.layout code, as a python dict
93130
'''
94-
base_app_inst = self._stateless_dash_app().as_dash_instance()
131+
base_app_inst = self.stateless_app.as_dash_app().as_dash_instance()
95132

96133
# Get base layout response, from a base object
97134
base_resp = base_app_inst.locate_endpoint_function('dash-layout')()
@@ -113,21 +150,30 @@ def populate_values(self):
113150
@staticmethod
114151
def locate_item(id, stateless=False):
115152
if stateless:
116-
da = get_stateless_by_name(id)
153+
da = find_stateless_by_name(id)
117154
else:
118155
da = get_object_or_404(DashApp,slug=id)
119156

120157
app = da.as_dash_instance()
121158
return da, app
122159

123160
class DashAppAdmin(admin.ModelAdmin):
124-
list_display = ['instance_name','app_name','slug','creation','update','save_on_change',]
125-
list_filter = ['creation','update','save_on_change','app_name',]
161+
list_display = ['instance_name','stateless_app','slug','creation','update','save_on_change',]
162+
list_filter = ['creation','update','save_on_change','stateless_app',]
126163

127164
def _populate_values(self, request, queryset):
128165
for da in queryset:
129166
da.populate_values()
130167
da.save()
131-
_populate_values.short_description = "Populate app"
168+
_populate_values.short_description = "Populate app instance"
169+
170+
def _clone(self, request, queryset):
171+
for da in queryset:
172+
nda = DashApp(stateless_app=da.stateless_app,
173+
base_state=da.base_state,
174+
save_on_change=da.save_on_change)
175+
nda.save()
176+
177+
_clone.short_description = "Clone app instance"
132178

133-
actions = ['_populate_values',]
179+
actions = ['_populate_values','_clone',]

0 commit comments

Comments
 (0)