diff --git a/README.md b/README.md index fedca74d..edfc9794 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,14 @@ Expose [plotly dash](https://plot.ly/products/dash/) apps as django tags. See the source for this project here: -Online documentation can be found here: +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 + ## Installation First, install the package. This will also install plotly and some dash packages if they are not already present. @@ -18,7 +23,7 @@ Then, just add `django_plotly_dash` to `INSTALLED_APPS` in your Django `settings INSTALLED_APPS = [ ... - 'django_plotly_dash', + 'django_plotly_dash.apps.DjangoPlotlyDashConfig', ... ] @@ -110,6 +115,6 @@ templates: ``` The registration code needs to be in a location -that will be imported into the Django process before any template tag attempts to use it. The example Django application +that will be imported into the Django process before any model or template tag attempts to use it. The example Django application in the demo subdirectory achieves this through an import in the main urls.py file; any views.py would also be sufficient. diff --git a/demo/configdb.py b/demo/configdb.py new file mode 100644 index 00000000..08fb0fd9 --- /dev/null +++ b/demo/configdb.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# +# Create a new superuser +from django.contrib.auth import get_user_model + +UserModel = get_user_model() + +name="admin" +password="admin" + +try: + UserModel.objects.get(username=name) +except: + su = UserModel.objects.create_user(name,password=password) + su.is_staff=True + su.is_superuser=True + su.save() diff --git a/demo/demo/plotly_apps.py b/demo/demo/plotly_apps.py index f570b385..dee9c85e 100644 --- a/demo/demo/plotly_apps.py +++ b/demo/demo/plotly_apps.py @@ -44,7 +44,7 @@ def callback_size(dropdown_color, dropdown_size): html.Div(id="output-one") ]) -@a2.callback( +@a2.expanded_callback( dash.dependencies.Output('output-one','children'), [dash.dependencies.Input('dropdown-one','value')] ) diff --git a/demo/demo/settings.py b/demo/demo/settings.py index 08964954..aeae9699 100644 --- a/demo/demo/settings.py +++ b/demo/demo/settings.py @@ -38,7 +38,7 @@ 'django.contrib.messages', 'django.contrib.staticfiles', - 'django_plotly_dash', + 'django_plotly_dash.apps.DjangoPlotlyDashConfig', ] MIDDLEWARE = [ diff --git a/demo/demo/templates/index.html b/demo/demo/templates/index.html index 7749878a..2ad76c99 100644 --- a/demo/demo/templates/index.html +++ b/demo/demo/templates/index.html @@ -5,7 +5,7 @@
Content here - {%plotly_item "SimpleExample"%} + {%plotly_item "simpleexample-1" ratio=0.2 %}
Content here diff --git a/dev_requirements.txt b/dev_requirements.txt index a3b7d1d1..5b220038 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -10,7 +10,6 @@ dash-html-components==0.10.1 dash-renderer==0.12.1 decorator==4.3.0 Django==2.0.5 --e git+https://github.com/delsim/django-plotly-dash.git@293b454ee8965422155a971d9e417878dc89ce82#egg=django_plotly_dash docopt==0.6.2 docutils==0.14 Flask==1.0.2 diff --git a/django_plotly_dash/__init__.py b/django_plotly_dash/__init__.py index 3eb7c6b7..21100bbf 100644 --- a/django_plotly_dash/__init__.py +++ b/django_plotly_dash/__init__.py @@ -1,6 +1,6 @@ # -__version__ = "0.0.3" +__version__ = "0.0.4" from .dash_wrapper import DelayedDash diff --git a/django_plotly_dash/admin.py b/django_plotly_dash/admin.py index 8c38f3f3..4580ac5b 100644 --- a/django_plotly_dash/admin.py +++ b/django_plotly_dash/admin.py @@ -1,3 +1,6 @@ from django.contrib import admin -# Register your models here. +from .models import DashApp, DashAppAdmin + +admin.site.register(DashApp, DashAppAdmin) + diff --git a/django_plotly_dash/app_name.py b/django_plotly_dash/app_name.py new file mode 100644 index 00000000..bcf13f1e --- /dev/null +++ b/django_plotly_dash/app_name.py @@ -0,0 +1,2 @@ +app_name = "the_django_plotly_dash" +main_view_label = "main" diff --git a/django_plotly_dash/apps.py b/django_plotly_dash/apps.py index 5109ed21..e5e5fa7a 100644 --- a/django_plotly_dash/apps.py +++ b/django_plotly_dash/apps.py @@ -3,3 +3,4 @@ class DjangoPlotlyDashConfig(AppConfig): name = 'django_plotly_dash' + verbose_name = "Django Plotly Dash" diff --git a/django_plotly_dash/dash_wrapper.py b/django_plotly_dash/dash_wrapper.py index b03709b7..0f140238 100644 --- a/django_plotly_dash/dash_wrapper.py +++ b/django_plotly_dash/dash_wrapper.py @@ -2,19 +2,63 @@ from flask import Flask from django.urls import reverse +from django.http import HttpResponse + +import json + +from plotly.utils import PlotlyJSONEncoder + +from .app_name import app_name, main_view_label uid_counter = 0 usable_apps = {} -app_instances = {} nd_apps = {} +def add_usable_app(name, app): + global usable_apps + usable_apps[name] = app + +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): + ''' + Locate an instance of a dash app by identifier, or return None if one does not exist + ''' 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() + +class Holder: + def __init__(self): + self.items = [] + def append_css(self, stylesheet): + self.items.append(stylesheet) + def append_script(self, script): + self.items.append(script) + class DelayedDash: def __init__(self, name=None, **kwargs): if name is None: @@ -24,27 +68,32 @@ def __init__(self, name=None, **kwargs): else: self._uid = name self.layout = None - self._rep_dash = None self._callback_sets = [] - global usable_apps - usable_apps[self._uid] = self + self.css = Holder() + self.scripts = Holder() + + add_usable_app(self._uid, + self) - def _RepDash(self): - if self._rep_dash is None: - self._rep_dash = self._form_repdash() - return self._rep_dash + self._expanded_callbacks = False - def _form_repdash(self): + def form_dash_instance(self, replacements=None, specific_identifier=None): rd = NotDash(name_root=self._uid, - app_pathname="django_plotly_dash:main") + app_pathname="%s:%s" % (app_name, main_view_label), + expanded_callbacks = self._expanded_callbacks, + replacements = replacements, + specific_identifier = specific_identifier) rd.layout = self.layout + for cb, func in self._callback_sets: rd.callback(**cb)(func) - return rd + for s in self.css.items: + rd.css.append_css(s) + for s in self.scripts.items: + rd.scripts.append_script(s) - def base_url(self): - return self._RepDash().base_url() + return rd def callback(self, output, inputs=[], state=[], events=[]): callback_set = {'output':output, @@ -56,6 +105,10 @@ def wrap_func(func,callback_set=callback_set,callback_sets=self._callback_sets): return func return wrap_func + def expanded_callback(self, output, inputs=[], state=[], events=[]): + self._expanded_callbacks = True + return self.callback(output, inputs, state, events) + class NotFlask: def __init__(self): self.config = {} @@ -74,17 +127,14 @@ def run(self,*args,**kwargs): pass class NotDash(Dash): - def __init__(self, name_root, app_pathname, **kwargs): - - global app_instances - current_instances = app_instances.get(name_root,None) + def __init__(self, name_root, app_pathname=None, replacements = None, specific_identifier=None, expanded_callbacks=False, **kwargs): - if current_instances is not None: - self._uid = "%s-%i" % (name_root,len(current_instances)+1) - current_instances.append(self) + if specific_identifier is not None: + self._uid = specific_identifier else: self._uid = name_root - app_instances[name_root] = [self,] + + add_instance(self._uid, self) self._flask_app = Flask(self._uid) self._notflask = NotFlask() @@ -92,12 +142,74 @@ def __init__(self, name_root, app_pathname, **kwargs): kwargs['url_base_pathname'] = self._base_pathname kwargs['server'] = self._notflask + super(NotDash, self).__init__(**kwargs) - global nd_apps - nd_apps[self._uid] = self - if False: # True for some debug info and a load of errors... - self.css.config.serve_locally = True - self.scripts.config.serve_locally = True + + self._adjust_id = False + self._dash_dispatch = not expanded_callbacks + if replacements: + self._replacements = replacements + else: + self._replacements = dict() + self._use_dash_layout = len(self._replacements) < 1 + + def use_dash_dispatch(self): + return self._dash_dispatch + + def use_dash_layout(self): + return self._use_dash_layout + + def augment_initial_layout(self, base_response): + if self.use_dash_layout() and False: + return HttpResponse(base_response.data, + content_type=base_response.mimetype) + # Adjust the base layout response + baseDataInBytes = base_response.data + baseData = json.loads(baseDataInBytes.decode('utf-8')) + # Walk tree. If at any point we have an element whose id matches, then replace any named values at this level + reworked_data = self.walk_tree_and_replace(baseData) + response_data = json.dumps(reworked_data, + cls=PlotlyJSONEncoder) + return HttpResponse(response_data, + content_type=base_response.mimetype) + + def walk_tree_and_extract(self, data, target): + if isinstance(data, dict): + for key in ['children','props',]: + self.walk_tree_and_extract(data.get(key,None),target) + ident = data.get('id', None) + if ident is not None: + idVals = target.get(ident,{}) + for key, value in data.items(): + if key not in ['props','options','children','id']: + idVals[key] = value + if len(idVals) > 0: + target[ident] = idVals + if isinstance(data, list): + for element in data: + self.walk_tree_and_extract(element, target) + + def walk_tree_and_replace(self, data): + # Walk the tree. Rely on json decoding to insert instances of dict and list + # ie we use a dna test for anatine, rather than our eyes and ears... + if isinstance(data,dict): + response = {} + replacements = {} + # look for id entry + thisID = data.get('id',None) + if thisID is not None: + replacements = self._replacements.get(thisID,{}) + # walk all keys and replace if needed + for k, v in data.items(): + r = replacements.get(k,None) + if r is None: + r = self.walk_tree_and_replace(v) + response[k] = r + return response + if isinstance(data,list): + # process each entry in turn and return + return [self.walk_tree_and_replace(x) for x in data] + return data def flask_app(self): return self._flask_app @@ -123,10 +235,13 @@ def locate_endpoint_function(self, name=None): @Dash.layout.setter def layout(self, value): - self._fix_component_id(value) + + if self._adjust_id: + self._fix_component_id(value) return Dash.layout.fset(self, value) def _fix_component_id(self, component): + theID = getattr(component,"id",None) if theID is not None: setattr(component,"id",self._fix_id(theID)) @@ -137,6 +252,8 @@ def _fix_component_id(self, component): pass def _fix_id(self, name): + if not self._adjust_id: + return name return "%s_-_%s" %(self._uid, name) @@ -150,3 +267,32 @@ def callback(self, output, inputs=[], state=[], events=[]): [self._fix_callback_item(x) for x in state], [self._fix_callback_item(x) for x in events]) + def dispatch(self): + import flask + body = flask.request.get_json() + return self. dispatch_with_args(body, argMap=dict()) + + def dispatch_with_args(self, body, argMap): + inputs = body.get('inputs', []) + state = body.get('state', []) + output = body['output'] + + 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) + + diff --git a/django_plotly_dash/migrations/0001_initial.py b/django_plotly_dash/migrations/0001_initial.py new file mode 100644 index 00000000..8cb034e5 --- /dev/null +++ b/django_plotly_dash/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 2.0.5 on 2018-05-10 21:48 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + 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()), + ('creation', models.DateTimeField(auto_now_add=True)), + ('update', models.DateTimeField(auto_now=True)), + ], + ), + ] diff --git a/django_plotly_dash/migrations/0002_simple_example_state.py b/django_plotly_dash/migrations/0002_simple_example_state.py new file mode 100644 index 00000000..c6cef1e3 --- /dev/null +++ b/django_plotly_dash/migrations/0002_simple_example_state.py @@ -0,0 +1,28 @@ +# Generated by Django 2.0.5 on 2018-05-10 23:26 + +from django.db import migrations + +def getDA(apps): + return apps.get_model("django_plotly_dash","DashApp") + +def forward(apps, schema_editor): + DashApp = getDA(apps) + da = DashApp(app_name="SimpleExample", + instance_name="SimpleExample-1", + slug="simpleexample-1", + base_state='{"dropdown-color":{"value":"blue"},"dropdown-size":{"value":"small"}}') + da.save() + +def backward(apps, schema_editor): + DashApp = getDA(apps) + DashApp.objects.all().delete() + +class Migration(migrations.Migration): + + dependencies = [ + ('django_plotly_dash', '0001_initial'), + ] + + operations = [ + migrations.RunPython(forward, backward), + ] diff --git a/django_plotly_dash/migrations/__init__.py b/django_plotly_dash/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/django_plotly_dash/models.py b/django_plotly_dash/models.py index 71a83623..aee3221d 100644 --- a/django_plotly_dash/models.py +++ b/django_plotly_dash/models.py @@ -1,3 +1,96 @@ from django.db import models +from django.contrib import admin +from django.utils.text import slugify -# Create your models here. +from .dash_wrapper import get_app_instance_by_id, get_app_by_name, clear_app_instance + +import json + +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) + 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) # If mandating postgresql then this could be a JSONField + creation = models.DateTimeField(auto_now_add=True) + update = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.instance_name + + 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) + if not self.slug or len(self.slug) < 2: + self.slug = slugify(self.instance_name) + super(DashApp, self).save(*args,**kwargs) + # TODO at this point should invaliate any local cache of the older values + clear_app_instance(self.slug) + + def as_dash_instance(self): + ai = get_app_instance_by_id(self.slug) + if ai: + return ai + dd = get_app_by_name(self.app_name) + base = json.loads(self.base_state) + return dd.form_dash_instance(replacements=base, + specific_identifier=self.slug) + + @staticmethod + def get_app_instance(id): + ''' + Locate an application instance by id, either in local cache or in Database. + If in neither, then create a new instance assuming that the id is the app name. + ''' + local_instance = get_app_instance_by_id(id) + if local_instance: + return local_instance + try: + return DashApp.objects.get(slug=id).as_dash_instance() + except: + pass + + # Really no luck at all! + dd = get_app_by_name(id) + return dd.form_dash_instance() + + def _get_base_state(self): + ''' + Get the base state of the object, as defined by the app.layout code, as a python dict + ''' + # Get base layout response, from a base object + base_app_inst = get_app_instance_by_id(self.app_name) + if not base_app_inst: + base_app = get_app_by_name(self.app_name) + base_app_inst = base_app.form_dash_instance() + + base_resp = base_app_inst.locate_endpoint_function('dash-layout')() + + base_obj = json.loads(base_resp.data.decode('utf-8')) + + # Walk the base layout and find all values; insert into base state map + obj = {} + base_app_inst.walk_tree_and_extract(base_obj, obj) + return obj + + def populate_values(self): + ''' + Add values from the underlying dash layout configuration + ''' + obj = self._get_base_state() + self.base_state = json.dumps(obj) + +class DashAppAdmin(admin.ModelAdmin): + list_display = ['instance_name','app_name','slug','creation','update',] + list_filter = ['app_name','creation','update',] + + def _populate_values(self, request, queryset): + for da in queryset: + da.populate_values() + da.save() + _populate_values.short_description = "Populate app" + + actions = ['_populate_values',] diff --git a/django_plotly_dash/templates/django_plotly_dash/plotly_item.html b/django_plotly_dash/templates/django_plotly_dash/plotly_item.html index 23d49438..bbe22d2c 100644 --- a/django_plotly_dash/templates/django_plotly_dash/plotly_item.html +++ b/django_plotly_dash/templates/django_plotly_dash/plotly_item.html @@ -1,3 +1,3 @@ -
- +
+
diff --git a/django_plotly_dash/templatetags/dexy.py b/django_plotly_dash/templatetags/dexy.py deleted file mode 100644 index 52fce6e5..00000000 --- a/django_plotly_dash/templatetags/dexy.py +++ /dev/null @@ -1,13 +0,0 @@ -from django import template - -register = template.Library() - -from django_plotly_dash.dash_wrapper import get_app_by_name - -@register.inclusion_tag("django_plotly_dash/plotly_item.html", takes_context=True) -def plotly_item(context, app_name): - - app = get_app_by_name(app_name) - url = app.base_url() - - return locals() diff --git a/django_plotly_dash/templatetags/plotly_dash.py b/django_plotly_dash/templatetags/plotly_dash.py index 52fce6e5..567f6e56 100644 --- a/django_plotly_dash/templatetags/plotly_dash.py +++ b/django_plotly_dash/templatetags/plotly_dash.py @@ -2,12 +2,28 @@ register = template.Library() -from django_plotly_dash.dash_wrapper import get_app_by_name +from django_plotly_dash.models import DashApp @register.inclusion_tag("django_plotly_dash/plotly_item.html", takes_context=True) -def plotly_item(context, app_name): +def plotly_item(context, app_name, ratio=0.1, use_frameborder=False): - app = get_app_by_name(app_name) - url = app.base_url() + fbs = use_frameborder and '1' or '0' + + dstyle = """ + position: relative; + padding-bottom: %s%%; + height: 0; + overflow:hidden; + """ % (ratio*100) + + istyle = """ + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + """ + + app = DashApp.get_app_instance(app_name) return locals() diff --git a/django_plotly_dash/urls.py b/django_plotly_dash/urls.py index 375cbbca..1d6696b6 100644 --- a/django_plotly_dash/urls.py +++ b/django_plotly_dash/urls.py @@ -3,7 +3,7 @@ from .views import routes, layout, dependencies, update, main_view -app_name = "django_plotly_dash" +from .app_name import app_name, main_view_label urlpatterns = [ path('_dash-routes', routes, name="routes"), @@ -11,6 +11,6 @@ path('_dash-dependencies', dependencies, name="dependencies"), path('_dash-update-component', csrf_exempt(update), name="update-component"), - path('', main_view, name="main"), + path('', main_view, name=main_view_label), ] diff --git a/django_plotly_dash/views.py b/django_plotly_dash/views.py index 92dc0a95..7247992c 100644 --- a/django_plotly_dash/views.py +++ b/django_plotly_dash/views.py @@ -1,16 +1,15 @@ from django.shortcuts import render +from django.http import HttpResponse -import flask import json -from .dash_wrapper import get_app_instance_by_id -from django.http import HttpResponse +from .models import DashApp def routes(*args,**kwargs): pass def dependencies(request, id, **kwargs): - app = get_app_instance_by_id(id) + app = DashApp.get_app_instance(id) with app.app_context(): mFunc = app.locate_endpoint_function('dash-dependencies') resp = mFunc() @@ -18,26 +17,35 @@ def dependencies(request, id, **kwargs): content_type=resp.mimetype) def layout(request, id, **kwargs): - app = get_app_instance_by_id(id) + app = DashApp.get_app_instance(id) mFunc = app.locate_endpoint_function('dash-layout') resp = mFunc() - return HttpResponse(resp.data, - content_type=resp.mimetype) + return app.augment_initial_layout(resp) def update(request, id, **kwargs): - app = get_app_instance_by_id(id) - mFunc = app.locate_endpoint_function('dash-update-component') - # Fudge request object + app = DashApp.get_app_instance(id) rb = json.loads(request.body.decode('utf-8')) - with app.test_request_context(): - # inputs state and output needed in the json objects - flask.request._cached_json = (rb, flask.request._cached_json[True]) - resp = mFunc() + + if app.use_dash_dispatch(): + # Force call through dash + mFunc = app.locate_endpoint_function('dash-update-component') + + import flask + with app.test_request_context(): + # Fudge request object + flask.request._cached_json = (rb, flask.request._cached_json[True]) + resp = mFunc() + else: + # Use direct dispatch with extra arguments in the argMap + argMap = {'id':id, + 'session':request.session} + resp = app.dispatch_with_args(rb, argMap) + return HttpResponse(resp.data, content_type=resp.mimetype) def main_view(request, id, **kwargs): - app = get_app_instance_by_id(id) + app = DashApp.get_app_instance(id) mFunc = app.locate_endpoint_function() resp = mFunc() return HttpResponse(resp) diff --git a/docs/index.rst b/docs/index.rst index 0a169a27..06d66d94 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,8 +3,10 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to django-plotly-dash's documentation! -============================================== +django-plotly-dash +================== + +`Plotly Dash `_ applications served up in Django templates using tags. Contents -------- @@ -12,6 +14,7 @@ Contents .. toctree:: :maxdepth: 2 + introduction installation simple_use diff --git a/docs/installation.rst b/docs/installation.rst index ee12c752..b745f6b0 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -11,11 +11,14 @@ Then, add ``django_plotly_dash`` to ``INSTALLED_APPS`` in the Django settings.py INSTALLED_APPS = [ ... - 'django_plotly_dash', + 'django_plotly_dash.apps.DjangoPlotlyDashConfig', ... ] -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 ``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. Source code and demo -------------------- @@ -36,3 +39,4 @@ To install and run it:: # using the Django debug server # 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``. diff --git a/docs/introduction.rst b/docs/introduction.rst new file mode 100644 index 00000000..9ade7119 --- /dev/null +++ b/docs/introduction.rst @@ -0,0 +1,15 @@ +.. _introduction: + +Introduction +============ + +The purpose of django-plotly-dash is to enable Plotly Dash applications to be served up as part of a Django application, in order to provide +these features: + +* Multiple dash applications can be used on a single page +* Separate instances of a dash application can persist along with internal state +* Leverage user management and access control and other parts of the Django infrastructure +* Consolidate into a single server process to simplify scaling + +There is nothing here that cannot be achieved through expanding the Flask app around Plotly Dash, or indeed by using an alternative web +framework. The purpose of this project is to enable the above features, given that the choice to use Django has already been made. diff --git a/docs/simple_use.rst b/docs/simple_use.rst index d0d39825..a7fae581 100644 --- a/docs/simple_use.rst +++ b/docs/simple_use.rst @@ -4,9 +4,9 @@ Simple Usage ============ To use existing dash applications, first register them using the ``DelayedDash`` class. This -replaces the ``dash.Dash`` class of ``plotly.py`` +replaces the ``Dash`` class from the ``dash`` package. -Taking as an example a slightly modified variant of one of the `getting started `_ examples:: +Taking a simple example inspired by the excellent `getting started `_ guide:: import dash import dash_core_components as dcc @@ -54,7 +54,8 @@ templates::: {%plotly_item "SimpleExample"%} -Note that the registration code needs to be in a location -that will be imported into the Django process before any template tag attempts to use it. The example Django application +The registration code needs to be in a location +that will be imported into the Django process before any model or +template tag attempts to use it. The example Django application in the demo subdirectory achieves this through an import in the main urls.py file; any views.py would also be sufficient. diff --git a/make_env b/make_env index 82d9946e..85b5112a 100755 --- a/make_env +++ b/make_env @@ -3,3 +3,5 @@ virtualenv -p python3 env source env/bin/activate pip install -r requirements.txt +pip install -r dev_requirements.txt +python setup.py develop diff --git a/prepare_demo b/prepare_demo index 4021cd1c..2312ea7d 100755 --- a/prepare_demo +++ b/prepare_demo @@ -1,7 +1,7 @@ #!/usr/bin/env bash # source env/bin/activate -python setup.py develop cd demo ./manage.py migrate +./manage.py shell < configdb.py # Add a superuser if needed ./manage.py runserver