Skip to content

Commit 7b151ae

Browse files
delsimGibbsConsulting
authored andcommitted
Session data example (#114)
* v0.9.5 packages for future reference * Rough-in of Django session state example * Adapt boostrap demo * Rough-in of Django session state example * Adapt boostrap demo * Rough-in of Django session state example * Adapt boostrap demo * Extended session state demo to include state set in view and dash app * Added documentation of demo eight * Linter suggestions
1 parent c934aec commit 7b151ae

10 files changed

+231
-3
lines changed

demo/demo/bootstrap_app.py

+42
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
SOFTWARE.
2323
'''
2424

25+
import dash
2526
import dash_bootstrap_components as dbc
2627
import dash_html_components as html
2728

@@ -36,3 +37,44 @@
3637
dbc.Alert("Danger", color="danger"),
3738
]
3839
)
40+
41+
dis = DjangoDash("DjangoSessionState",
42+
add_bootstrap_links=True)
43+
44+
dis.layout = html.Div(
45+
[
46+
dbc.Alert("This is an alert", id="base-alert", color="primary"),
47+
dbc.Alert(children="Danger", id="danger-alert", color="danger"),
48+
dbc.Button("Update session state", id="update-button", color="warning"),
49+
]
50+
)
51+
52+
#pylint: ignore=unused-argument
53+
@dis.expanded_callback(
54+
dash.dependencies.Output("base-alert", 'children'),
55+
[dash.dependencies.Input('danger-alert', 'children'),]
56+
)
57+
def session_demo_danger_callback(da_children, session_state=None, **kwargs):
58+
'Update output based just on state'
59+
if not session_state:
60+
return "Session state not yet available"
61+
62+
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"))
63+
64+
#pylint: ignore=unused-argument
65+
@dis.expanded_callback(
66+
dash.dependencies.Output("danger-alert", 'children'),
67+
[dash.dependencies.Input('update-button', 'n_clicks'),]
68+
)
69+
def session_demo_alert_callback(n_clicks, session_state=None, **kwargs):
70+
'Output text based on both app state and session state'
71+
if session_state is None:
72+
raise NotImplementedError("Cannot handle a missing session state")
73+
csf = session_state.get('bootstrap_demo_state', None)
74+
if not csf:
75+
csf = dict(clicks=0)
76+
session_state['bootstrap_demo_state'] = csf
77+
else:
78+
csf['clicks'] = n_clicks
79+
return "Button has been clicked %s times since the page was rendered" %n_clicks
80+

demo/demo/templates/base.html

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
<a class="nav-item nav-link btn btn-lg" href="{%url "demo-four"%}">Four - Live Updating</a>
3131
<a class="nav-item nav-link btn btn-lg" href="{%url "demo-five"%}">Five - Direct Injection</a>
3232
<a class="nav-item nav-link btn btn-lg" href="{%url "demo-six"%}">Six - Simple Injection</a>
33+
<a class="nav-item nav-link btn btn-lg" href="{%url "demo-seven"%}">Seven - Boostrap Components</a>
34+
<a class="nav-item nav-link btn btn-lg" href="{%url "demo-seven"%}">Eight - Session State</a>
3335
{% endif %}
3436
{% endblock %}
3537
<a class="nav-item nav-link btn btn-lg"

demo/demo/templates/demo_eight.html

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{%extends "base.html"%}
2+
{%load plotly_dash%}
3+
4+
{%block title%}Demo Eight - Django Session State{%endblock%}
5+
6+
{%block content%}
7+
<h1>Django Session State</h1>
8+
<p>
9+
This example demonstrates passing session state values from Django to a Dash app.
10+
</p>
11+
<p>
12+
The view that renders this page updates a variable each time
13+
the view is rendered in this session. Clicking on the button will update the session
14+
with a record of the number of clicks of the button since the page was rendered.
15+
</p>
16+
<p>
17+
The current page render count in this session is <span>{{ind_use}}</span>
18+
</p>
19+
<p></p>
20+
<div class="card bg-light border-dark">
21+
<div class="card-body">
22+
<p><span>{</span>% load plotly_dash %}</p>
23+
<p>&lt;div class="<span>{</span>% plotly_class name="DjangoSessionState"%}">
24+
<p class="ml-3"><span>{</span>% plotly_app name="DjangoSessionState" ratio=0.3 %}</p>
25+
<p>&lt;\div>
26+
</div>
27+
</div>
28+
<p></p>
29+
<div class="card border-dark">
30+
<div class="card-body">
31+
<div class="{%plotly_class name="DjangoSessionState"%}">
32+
{%plotly_app name="DjangoSessionState" ratio=0.3 %}
33+
</div>
34+
</div>
35+
</div>
36+
{%endblock%}

demo/demo/templates/index.html

+1
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ <h1>Demonstration Application</h1>
1414
<li><a class="btn btn-primary btnspace" href="{%url "demo-five"%}">Demo Five</a> - injection of a Dash application without embedding in an html iframe</li>
1515
<li><a class="btn btn-primary btnspace" href="{%url "demo-six"%}">Demo Six</a> - simple html injection example</li>
1616
<li><a class="btn btn-primary btnspace" href="{%url "demo-seven"%}">Demo Seven</a> - dash-bootstrap-components example</li>
17+
<li><a class="btn btn-primary btnspace" href="{%url "demo-eight"%}">Demo Eight</a> - Django session state example</li>
1718
</ul>
1819
{%endblock%}

demo/demo/urls.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
from django_plotly_dash.views import add_to_session
3434

35-
from .views import dash_example_1_view
35+
from .views import dash_example_1_view, session_state_view
3636

3737
urlpatterns = [
3838
url('^$', TemplateView.as_view(template_name='index.html'), name="home"),
@@ -43,6 +43,7 @@
4343
url('^demo-five$', TemplateView.as_view(template_name='demo_five.html'), name="demo-five"),
4444
url('^demo-six', dash_example_1_view, name="demo-six"),
4545
url('^demo-seven', TemplateView.as_view(template_name='demo_seven.html'), name="demo-seven"),
46+
url('^demo-eight', session_state_view, {'template_name':'demo_eight.html'}, name="demo-eight"),
4647
url('^admin/', admin.site.urls),
4748
url('^django_plotly_dash/', include('django_plotly_dash.urls')),
4849

demo/demo/views.py

+17
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,20 @@ def dash_example_1_view(request, template_name="demo_six.html", **kwargs):
1717
request.session['django_plotly_dash'] = dash_context
1818

1919
return render(request, template_name=template_name, context=context)
20+
21+
def session_state_view(request, template_name, **kwargs):
22+
'Example view that exhibits the use of sessions to store state'
23+
24+
session = request.session
25+
26+
demo_count = session.get('django_plotly_dash', {})
27+
28+
ind_use = demo_count.get('ind_use', 0)
29+
ind_use += 1
30+
demo_count['ind_use'] = ind_use
31+
32+
context = {'ind_use' : ind_use}
33+
34+
session['django_plotly_dash'] = demo_count
35+
36+
return render(request, template_name=template_name, context=context)

docs/demo_notes.rst

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
.. _demo_notes:
2+
3+
Demonstration application
4+
=========================
5+
6+
There are a number of pages in the demo application in the
7+
source repository.
8+
9+
#. Direct insertion of one or more dash applications
10+
#. Initial state storage within Django
11+
#. Enhanced callbacks
12+
#. Live updating
13+
#. Injection without using an iframe
14+
#. Simple html injection
15+
#. Bootstrap components
16+
#. Session state storage
17+
18+
The templates that drive each of these can be found in
19+
the `github repository <https://github.com/GibbsConsulting/django-plotly-dash/tree/master/demo/demo/templates>`_.
20+
21+
There is a more details walkthrough of the :ref:`session state storage <session_example>` example. This example also
22+
shows the use of `dash bootstrap components <https://pypi.org/project/dash-bootstrap-components/>`_.
23+
24+
.. _session_example:
25+
Session state example walkthrough
26+
---------------------------------
27+
28+
The session state example has three separate components in the demo application
29+
30+
* A template to render the application
31+
* The ``django-plotly-dash`` application itself
32+
* A view to render the template having initialised the session state if needed
33+
34+
The first of these is a standard Django template, containing instructions to
35+
render the Dash application::
36+
37+
{%load plotly-dash%}
38+
39+
...
40+
41+
<div class="{%plotly_class name="DjangoSessionState"%}">
42+
{%plotly_app name="DjangoSessionState" ratio=0.3 %}
43+
</div>
44+
45+
The view sets up the initial state of the application prior to rendering. For this example
46+
we have a simple variant of rendering a template view::
47+
48+
def session_state_view(request, template_name, **kwargs):
49+
50+
# Set up a context dict here
51+
context = { ... values for template go here, see below ... }
52+
53+
return render(request, template_name=template_name, context=context)
54+
55+
and it suffices to register this view at a convenient URL as it does not
56+
use any parameters::
57+
58+
...
59+
url('^demo-eight',
60+
session_state_view,
61+
{'template_name':'demo_eight.html'},
62+
name="demo-eight"),
63+
...
64+
65+
In passing, we note that accepting parameters as part of the URL and passing them as initial
66+
parameters to the app through the template is a straightforward extension of this example.
67+
68+
The session state can be accessed in the app as well as the view. The app is essentially formed
69+
from a layout function and a number of callbacks. In this particular example,
70+
`dash-bootstrap-components <https://dash-bootstrap-components.opensource.asidatascience.com/>`_
71+
are used to form the layout::
72+
73+
dis = DjangoDash("DjangoSessionState",
74+
add_bootstrap_links=True)
75+
76+
dis.layout = html.Div(
77+
[
78+
dbc.Alert("This is an alert", id="base-alert", color="primary"),
79+
dbc.Alert(children="Danger", id="danger-alert", color="danger"),
80+
dbc.Button("Update session state", id="update-button", color="warning"),
81+
]
82+
)
83+
84+
Within the :ref:`expanded callback <extended_callbacks>`, the session state is passed as an extra
85+
argument compared to the standard ``Dash`` callback::
86+
87+
@dis.expanded_callback(
88+
dash.dependencies.Output("danger-alert", 'children'),
89+
[dash.dependencies.Input('update-button', 'n_clicks'),]
90+
)
91+
def session_demo_danger_callback(n_clicks, session_state=None, **kwargs):
92+
if session_state is None:
93+
raise NotImplementedError("Cannot handle a missing session state")
94+
csf = session_state.get('bootstrap_demo_state', None)
95+
if not csf:
96+
csf = dict(clicks=0)
97+
session_state['bootstrap_demo_state'] = csf
98+
else:
99+
csf['clicks'] = n_clicks
100+
return "Button has been clicked %s times since the page was rendered" %n_clicks
101+
102+
The session state is also set during the view::
103+
104+
def session_state_view(request, template_name, **kwargs):
105+
106+
session = request.session
107+
108+
demo_count = session.get('django_plotly_dash', {})
109+
110+
ind_use = demo_count.get('ind_use', 0)
111+
ind_use += 1
112+
demo_count['ind_use'] = ind_use
113+
session['django_plotly_dash'] = demo_count
114+
115+
# Use some of the information during template rendering
116+
context = {'ind_use' : ind_use}
117+
118+
return render(request, template_name=template_name, context=context)
119+
120+
Reloading the demonstration page will cause the page render count to be incremented, and the
121+
button click count to be reset. Loading the page in a different session, for example by using
122+
a different browser or machine, will have an independent render count.
123+
124+

docs/extended_callbacks.rst

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ in the :ref:`models_and_state` section.
5454
Using session state
5555
-------------------
5656

57+
The :ref:`walkthrough <session_example>` of the session state example details how
58+
the XXX demo interacts with a ``Django`` session.
59+
5760
Unless an explicit pipe is created, changes to the session state and other server-side objects are not automatically
5861
propagated to an application. Something in the front-end UI has to invoke a callback; at this point the
5962
latest version of these objects will be provided to the callback. The same considerations

docs/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Contents
2323
template_tags
2424
dash_components
2525
configuration
26+
demo_notes
2627
access_control
2728
faq
2829
development

docs/installation.rst

+3-2
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ This includes arranging for Dash assets to be served using the Django ``staticfi
9090
Source code and demo
9191
--------------------
9292

93-
The source code repository contains a simple demo application.
93+
The source code repository contains a :ref:`simple demo <demo_notes>` application.
9494

9595
To install and run it::
9696

@@ -116,5 +116,6 @@ the ``prepare_redis`` step is skipped then the fourth demo page, exhibiting live
116116
More details on setting up a development environment, which is also sufficient for running
117117
the demo, can be found in the :ref:`development <development>` section.
118118

119-
Note that the current demo, along with the codebase, is in a prerelease and very raw form.
119+
Note that the current demo, along with the codebase, is in a prerelease and very raw form. An
120+
overview can be found in the :ref:`demonstration application<demo_notes>` section.`
120121

0 commit comments

Comments
 (0)