diff --git a/demo/demo/bootstrap_app.py b/demo/demo/bootstrap_app.py index 207df220..a06220d6 100644 --- a/demo/demo/bootstrap_app.py +++ b/demo/demo/bootstrap_app.py @@ -22,6 +22,7 @@ SOFTWARE. ''' +import dash import dash_bootstrap_components as dbc import dash_html_components as html @@ -36,3 +37,44 @@ dbc.Alert("Danger", color="danger"), ] ) + +dis = DjangoDash("DjangoSessionState", + add_bootstrap_links=True) + +dis.layout = html.Div( + [ + dbc.Alert("This is an alert", id="base-alert", color="primary"), + dbc.Alert(children="Danger", id="danger-alert", color="danger"), + dbc.Button("Update session state", id="update-button", color="warning"), + ] + ) + +#pylint: ignore=unused-argument +@dis.expanded_callback( + dash.dependencies.Output("base-alert", 'children'), + [dash.dependencies.Input('danger-alert', 'children'),] + ) +def session_demo_danger_callback(da_children, session_state=None, **kwargs): + 'Update output based just on state' + if not session_state: + return "Session state not yet available" + + return "Session state contains: " + str(session_state.get('bootstrap_demo_state', "NOTHING")) + " and the page render count is " + str(session_state.get("ind_use", "NOT SET")) + +#pylint: ignore=unused-argument +@dis.expanded_callback( + dash.dependencies.Output("danger-alert", 'children'), + [dash.dependencies.Input('update-button', 'n_clicks'),] + ) +def session_demo_alert_callback(n_clicks, session_state=None, **kwargs): + 'Output text based on both app state and session state' + if session_state is None: + raise NotImplementedError("Cannot handle a missing session state") + csf = session_state.get('bootstrap_demo_state', None) + if not csf: + csf = dict(clicks=0) + session_state['bootstrap_demo_state'] = csf + else: + csf['clicks'] = n_clicks + return "Button has been clicked %s times since the page was rendered" %n_clicks + diff --git a/demo/demo/templates/base.html b/demo/demo/templates/base.html index 9d7eafda..740aed37 100644 --- a/demo/demo/templates/base.html +++ b/demo/demo/templates/base.html @@ -30,6 +30,8 @@ Four - Live Updating Five - Direct Injection Six - Simple Injection + Seven - Boostrap Components + Eight - Session State {% endif %} {% endblock %} Django Session State +

+ This example demonstrates passing session state values from Django to a Dash app. +

+

+ The view that renders this page updates a variable each time + the view is rendered in this session. Clicking on the button will update the session + with a record of the number of clicks of the button since the page was rendered. +

+

+ The current page render count in this session is {{ind_use}} +

+

+
+
+

{% load plotly_dash %}

+

<div class="{% plotly_class name="DjangoSessionState"%}"> +

{% plotly_app name="DjangoSessionState" ratio=0.3 %}

+

<\div> +

+
+

+
+
+
+ {%plotly_app name="DjangoSessionState" ratio=0.3 %} +
+
+
+{%endblock%} diff --git a/demo/demo/templates/index.html b/demo/demo/templates/index.html index 7f419473..d6e727ad 100644 --- a/demo/demo/templates/index.html +++ b/demo/demo/templates/index.html @@ -14,5 +14,6 @@

Demonstration Application

  • Demo Five - injection of a Dash application without embedding in an html iframe
  • Demo Six - simple html injection example
  • Demo Seven - dash-bootstrap-components example
  • +
  • Demo Eight - Django session state example
  • {%endblock%} diff --git a/demo/demo/urls.py b/demo/demo/urls.py index 75d148a3..0db3cf9e 100644 --- a/demo/demo/urls.py +++ b/demo/demo/urls.py @@ -32,7 +32,7 @@ from django_plotly_dash.views import add_to_session -from .views import dash_example_1_view +from .views import dash_example_1_view, session_state_view urlpatterns = [ url('^$', TemplateView.as_view(template_name='index.html'), name="home"), @@ -43,6 +43,7 @@ url('^demo-five$', TemplateView.as_view(template_name='demo_five.html'), name="demo-five"), url('^demo-six', dash_example_1_view, name="demo-six"), url('^demo-seven', TemplateView.as_view(template_name='demo_seven.html'), name="demo-seven"), + url('^demo-eight', session_state_view, {'template_name':'demo_eight.html'}, name="demo-eight"), url('^admin/', admin.site.urls), url('^django_plotly_dash/', include('django_plotly_dash.urls')), diff --git a/demo/demo/views.py b/demo/demo/views.py index b73a2f1f..3b9559be 100644 --- a/demo/demo/views.py +++ b/demo/demo/views.py @@ -17,3 +17,20 @@ def dash_example_1_view(request, template_name="demo_six.html", **kwargs): request.session['django_plotly_dash'] = dash_context return render(request, template_name=template_name, context=context) + +def session_state_view(request, template_name, **kwargs): + 'Example view that exhibits the use of sessions to store state' + + session = request.session + + demo_count = session.get('django_plotly_dash', {}) + + ind_use = demo_count.get('ind_use', 0) + ind_use += 1 + demo_count['ind_use'] = ind_use + + context = {'ind_use' : ind_use} + + session['django_plotly_dash'] = demo_count + + return render(request, template_name=template_name, context=context) diff --git a/docs/demo_notes.rst b/docs/demo_notes.rst new file mode 100644 index 00000000..57eee2b8 --- /dev/null +++ b/docs/demo_notes.rst @@ -0,0 +1,124 @@ +.. _demo_notes: + +Demonstration application +========================= + +There are a number of pages in the demo application in the +source repository. + +#. Direct insertion of one or more dash applications +#. Initial state storage within Django +#. Enhanced callbacks +#. Live updating +#. Injection without using an iframe +#. Simple html injection +#. Bootstrap components +#. Session state storage + +The templates that drive each of these can be found in +the `github repository `_. + +There is a more details walkthrough of the :ref:`session state storage ` example. This example also +shows the use of `dash bootstrap components `_. + +.. _session_example: +Session state example walkthrough +--------------------------------- + +The session state example has three separate components in the demo application + +* A template to render the application +* The ``django-plotly-dash`` application itself +* A view to render the template having initialised the session state if needed + +The first of these is a standard Django template, containing instructions to +render the Dash application:: + + {%load plotly-dash%} + + ... + +
    + {%plotly_app name="DjangoSessionState" ratio=0.3 %} +
    + +The view sets up the initial state of the application prior to rendering. For this example +we have a simple variant of rendering a template view:: + + def session_state_view(request, template_name, **kwargs): + + # Set up a context dict here + context = { ... values for template go here, see below ... } + + return render(request, template_name=template_name, context=context) + +and it suffices to register this view at a convenient URL as it does not +use any parameters:: + + ... + url('^demo-eight', + session_state_view, + {'template_name':'demo_eight.html'}, + name="demo-eight"), + ... + +In passing, we note that accepting parameters as part of the URL and passing them as initial +parameters to the app through the template is a straightforward extension of this example. + +The session state can be accessed in the app as well as the view. The app is essentially formed +from a layout function and a number of callbacks. In this particular example, +`dash-bootstrap-components `_ +are used to form the layout:: + + dis = DjangoDash("DjangoSessionState", + add_bootstrap_links=True) + + dis.layout = html.Div( + [ + dbc.Alert("This is an alert", id="base-alert", color="primary"), + dbc.Alert(children="Danger", id="danger-alert", color="danger"), + dbc.Button("Update session state", id="update-button", color="warning"), + ] + ) + +Within the :ref:`expanded callback `, the session state is passed as an extra +argument compared to the standard ``Dash`` callback:: + + @dis.expanded_callback( + dash.dependencies.Output("danger-alert", 'children'), + [dash.dependencies.Input('update-button', 'n_clicks'),] + ) + def session_demo_danger_callback(n_clicks, session_state=None, **kwargs): + if session_state is None: + raise NotImplementedError("Cannot handle a missing session state") + csf = session_state.get('bootstrap_demo_state', None) + if not csf: + csf = dict(clicks=0) + session_state['bootstrap_demo_state'] = csf + else: + csf['clicks'] = n_clicks + return "Button has been clicked %s times since the page was rendered" %n_clicks + +The session state is also set during the view:: + + def session_state_view(request, template_name, **kwargs): + + session = request.session + + demo_count = session.get('django_plotly_dash', {}) + + ind_use = demo_count.get('ind_use', 0) + ind_use += 1 + demo_count['ind_use'] = ind_use + session['django_plotly_dash'] = demo_count + + # Use some of the information during template rendering + context = {'ind_use' : ind_use} + + return render(request, template_name=template_name, context=context) + +Reloading the demonstration page will cause the page render count to be incremented, and the +button click count to be reset. Loading the page in a different session, for example by using +a different browser or machine, will have an independent render count. + + diff --git a/docs/extended_callbacks.rst b/docs/extended_callbacks.rst index 346399e6..158ee819 100644 --- a/docs/extended_callbacks.rst +++ b/docs/extended_callbacks.rst @@ -54,6 +54,9 @@ in the :ref:`models_and_state` section. Using session state ------------------- +The :ref:`walkthrough ` of the session state example details how +the XXX demo interacts with a ``Django`` session. + Unless an explicit pipe is created, changes to the session state and other server-side objects are not automatically propagated to an application. Something in the front-end UI has to invoke a callback; at this point the latest version of these objects will be provided to the callback. The same considerations diff --git a/docs/index.rst b/docs/index.rst index 1a7a87db..5776165b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -23,6 +23,7 @@ Contents template_tags dash_components configuration + demo_notes access_control faq development diff --git a/docs/installation.rst b/docs/installation.rst index 5a0a92ac..cf4d554a 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -90,7 +90,7 @@ This includes arranging for Dash assets to be served using the Django ``staticfi Source code and demo -------------------- -The source code repository contains a simple demo application. +The source code repository contains a :ref:`simple demo ` application. To install and run it:: @@ -116,5 +116,6 @@ the ``prepare_redis`` step is skipped then the fourth demo page, exhibiting live More details on setting up a development environment, which is also sufficient for running the demo, can be found in the :ref:`development ` section. -Note that the current demo, along with the codebase, is in a prerelease and very raw form. +Note that the current demo, along with the codebase, is in a prerelease and very raw form. An +overview can be found in the :ref:`demonstration application` section.`