Skip to content

Commit

Permalink
Merge pull request #8 from delsim/master
Browse files Browse the repository at this point in the history
Move to 0.3.0
  • Loading branch information
GibbsConsulting committed Jun 8, 2018
2 parents 64448f0 + 53903b7 commit 578dcfe
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 107 deletions.
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.2.0"
__version__ = "0.3.0"

from .dash_wrapper import DjangoDash

5 changes: 4 additions & 1 deletion django_plotly_dash/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from django.contrib import admin

from .models import DashApp, DashAppAdmin
from .models import (DashApp, DashAppAdmin,
StatelessApp, StatelessAppAdmin,
)

admin.site.register(DashApp, DashAppAdmin)
admin.site.register(StatelessApp, StatelessAppAdmin)

44 changes: 23 additions & 21 deletions django_plotly_dash/dash_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def add_usable_app(name, app):
usable_apps[name] = app
return name

def get_stateless_by_name(name):
def get_local_stateless_by_name(name):
'''
Locate a registered dash app by name, and return a DjangoDash instance encapsulating the app.
'''
Expand Down Expand Up @@ -60,7 +60,7 @@ def as_dash_instance(self):
'''
Form a dash instance, for stateless use of this app
'''
return self.form_dash_instance()
return self.do_form_dash_instance()

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

try:
full_url = reverse(app_pathname,kwargs={'id':ndid})
except:
full_url = "/%s/" %ndid

full_url = reverse(app_pathname,kwargs={'id':ndid})
return ndid, full_url

def form_dash_instance(self, replacements=None, specific_identifier=None):
def do_form_dash_instance(self, replacements=None, specific_identifier=None):

ndid, base_pathname = self.get_base_pathname(specific_identifier)
return self.form_dash_instance(replacements, ndid, base_pathname)

def form_dash_instance(self, replacements=None, ndid=None, base_pathname=None):

if ndid is None:
ndid = self._uid

rd = NotDash(base_pathname=base_pathname,
expanded_callbacks = self._expanded_callbacks,
replacements = replacements,
ndid = ndid)
rd = WrappedDash(base_pathname=base_pathname,
expanded_callbacks = self._expanded_callbacks,
replacements = replacements,
ndid = ndid)

rd.layout = self.layout

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

class NotFlask:
class PseudoFlask:
def __init__(self):
self.config = {}
self.endpoints = {}
Expand All @@ -138,22 +140,22 @@ def before_first_request(self,*args,**kwargs):
def run(self,*args,**kwargs):
pass

class NotDash(Dash):
class WrappedDash(Dash):
def __init__(self, base_pathname=None, replacements = None, ndid=None, expanded_callbacks=False, **kwargs):

self._uid = ndid

self._flask_app = Flask(self._uid)
self._notflask = NotFlask()
self._notflask = PseudoFlask()
self._base_pathname = base_pathname

kwargs['url_base_pathname'] = self._base_pathname
kwargs['server'] = self._notflask

super(NotDash, self).__init__(**kwargs)
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 Expand Up @@ -273,10 +275,10 @@ def _fix_callback_item(self, item):
return item

def callback(self, output, inputs=[], state=[], events=[]):
return super(NotDash, self).callback(self._fix_callback_item(output),
[self._fix_callback_item(x) for x in inputs],
[self._fix_callback_item(x) for x in state],
[self._fix_callback_item(x) for x in events])
return super(WrappedDash, self).callback(self._fix_callback_item(output),
[self._fix_callback_item(x) for x in inputs],
[self._fix_callback_item(x) for x in state],
[self._fix_callback_item(x) for x in events])

def dispatch(self):
import flask
Expand Down
20 changes: 17 additions & 3 deletions django_plotly_dash/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Generated by Django 2.0.5 on 2018-05-10 21:48
# Generated by Django 2.0.5 on 2018-06-08 20:02

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):
Expand All @@ -15,12 +16,25 @@ class Migration(migrations.Migration):
name='DashApp',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('app_name', models.CharField(max_length=100)),
('instance_name', models.CharField(blank=True, max_length=100, unique=True)),
('slug', models.SlugField(blank=True, max_length=110, unique=True)),
('base_state', models.TextField()),
('base_state', models.TextField(default='{}')),
('creation', models.DateTimeField(auto_now_add=True)),
('update', models.DateTimeField(auto_now=True)),
('save_on_change', models.BooleanField(default=False)),
],
),
migrations.CreateModel(
name='StatelessApp',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('app_name', models.CharField(max_length=100, unique=True)),
('slug', models.SlugField(blank=True, max_length=110, unique=True)),
],
),
migrations.AddField(
model_name='dashapp',
name='stateless_app',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='django_plotly_dash.StatelessApp'),
),
]
36 changes: 36 additions & 0 deletions django_plotly_dash/migrations/0002_add_examples.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 2.0.5 on 2018-06-08 20:03

from django.db import migrations

def addExamples(apps, schema_editor):

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

sa1 = StatelessApp(app_name="SimpleExample",
slug="simple-example")

sa1.save()

da1 = DashApp(stateless_app=sa1,
instance_name="SimpleExample-1",
slug="simpleexample-1",
base_state='{"dropdown-color":{"value":"blue"},"dropdown-size":{"value":"small"}}')

da1.save()


def remExamples(apps, schema_editor):

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

class Migration(migrations.Migration):

dependencies = [
('django_plotly_dash', '0001_initial'),
]

operations = [
migrations.RunPython(addExamples, remExamples),
]
28 changes: 0 additions & 28 deletions django_plotly_dash/migrations/0002_simple_example_state.py

This file was deleted.

23 changes: 0 additions & 23 deletions django_plotly_dash/migrations/0003_auto_20180514_1802.py

This file was deleted.

86 changes: 66 additions & 20 deletions django_plotly_dash/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,59 @@
from django.utils.text import slugify
from django.shortcuts import get_object_or_404

from .dash_wrapper import get_stateless_by_name
from .dash_wrapper import get_local_stateless_by_name

import json

def get_stateless_by_name(name):
return get_local_stateless_by_name(name)

class StatelessApp(models.Model):
'''
A stateless Dash app. An instance of this model represents a dash app without any specific state
'''
app_name = models.CharField(max_length=100, blank=False, null=False, unique=True)
slug = models.SlugField(max_length=110, unique=True, blank=True)

def __str__(self):
return self.app_name

def save(self, *args, **kwargs):
if not self.slug or len(self.slug) < 2:
self.slug = slugify(self.app_name)
return super(StatelessApp, self).save(*args,**kwargs)

def as_dash_app(self):
'''
Return a DjangoDash instance of the dash application
'''
dd = getattr(self,'_stateless_dash_app_instance',None)
if not dd:
dd = get_stateless_by_name(self.app_name)
setattr(self,'_stateless_dash_app_instance',dd)
return dd

def find_stateless_by_name(name):
try:
dsa = StatelessApp.objects.get(app_name=name)
return dsa.as_dash_app()
except:
pass

da = get_stateless_by_name(name)
dsa = StatelessApp(app_name=name)
dsa.save()
return da

class StatelessAppAdmin(admin.ModelAdmin):
list_display = ['app_name','slug',]
list_filter = ['app_name','slug',]

class DashApp(models.Model):
'''
An instance of this model represents a dash application and its internal state
'''
app_name = models.CharField(max_length=100, blank=False, null=False, unique=False)
stateless_app = models.ForeignKey(StatelessApp, on_delete=models.PROTECT, unique=False, null=False, blank=False)
instance_name = models.CharField(max_length=100, unique=True, blank=True, null=False)
slug = models.SlugField(max_length=110, unique=True, blank=True)
base_state = models.TextField(null=False, default="{}") # If mandating postgresql then this could be a JSONField
Expand All @@ -24,19 +68,12 @@ def __str__(self):

def save(self, *args, **kwargs):
if not self.instance_name:
existing_count = DashApp.objects.filter(app_name=self.app_name).count()
self.instance_name = "%s-%i" %(self.app_name, existing_count+1)
existing_count = DashApp.objects.all().count()
self.instance_name = "%s-%i" %(self.stateless_app.app_name, existing_count+1)
if not self.slug or len(self.slug) < 2:
self.slug = slugify(self.instance_name)
super(DashApp, self).save(*args,**kwargs)

def _stateless_dash_app(self):
dd = getattr(self,'_stateless_dash_app_instance',None)
if not dd:
dd = get_stateless_by_name(self.app_name)
setattr(self,'_stateless_dash_app_instance',dd)
return dd

def handle_current_state(self):
'''
Check to see if the current hydrated state and the saved state are different.
Expand Down Expand Up @@ -82,16 +119,16 @@ def current_state(self):
return cs

def as_dash_instance(self):
dd = self._stateless_dash_app()
dd = self.stateless_app.as_dash_app()
base = self.current_state()
return dd.form_dash_instance(replacements=base,
specific_identifier=self.slug)
return dd.do_form_dash_instance(replacements=base,
specific_identifier=self.slug)

def _get_base_state(self):
'''
Get the base state of the object, as defined by the app.layout code, as a python dict
'''
base_app_inst = self._stateless_dash_app().as_dash_instance()
base_app_inst = self.stateless_app.as_dash_app().as_dash_instance()

# Get base layout response, from a base object
base_resp = base_app_inst.locate_endpoint_function('dash-layout')()
Expand All @@ -113,21 +150,30 @@ def populate_values(self):
@staticmethod
def locate_item(id, stateless=False):
if stateless:
da = get_stateless_by_name(id)
da = find_stateless_by_name(id)
else:
da = get_object_or_404(DashApp,slug=id)

app = da.as_dash_instance()
return da, app

class DashAppAdmin(admin.ModelAdmin):
list_display = ['instance_name','app_name','slug','creation','update','save_on_change',]
list_filter = ['creation','update','save_on_change','app_name',]
list_display = ['instance_name','stateless_app','slug','creation','update','save_on_change',]
list_filter = ['creation','update','save_on_change','stateless_app',]

def _populate_values(self, request, queryset):
for da in queryset:
da.populate_values()
da.save()
_populate_values.short_description = "Populate app"
_populate_values.short_description = "Populate app instance"

def _clone(self, request, queryset):
for da in queryset:
nda = DashApp(stateless_app=da.stateless_app,
base_state=da.base_state,
save_on_change=da.save_on_change)
nda.save()

_clone.short_description = "Clone app instance"

actions = ['_populate_values',]
actions = ['_populate_values','_clone',]

0 comments on commit 578dcfe

Please sign in to comment.