Skip to content

Commit 814c4a1

Browse files
author
Mark Gibbs
committed
First cut of direct html insertion of a dash app using a django template
1 parent ef92858 commit 814c4a1

File tree

6 files changed

+156
-28
lines changed

6 files changed

+156
-28
lines changed

demo/demo/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@
5151
'django.middleware.csrf.CsrfViewMiddleware',
5252
'django.contrib.auth.middleware.AuthenticationMiddleware',
5353
'django.contrib.messages.middleware.MessageMiddleware',
54+
55+
'django_plotly_dash.middleware.BaseMiddleware',
56+
5457
'django.middleware.clickjacking.XFrameOptionsMiddleware',
5558
]
5659

demo/demo/templates/base.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
{%block app_header_css%}
1111
<link rel="stylesheet" type="text/css" href="{%static "demo/demo.css"%}"></link>
1212
{%endblock%}
13+
{%plotly_header%}
1314
<title>Django Plotly Dash Examples - {%block title%}{%endblock%}</title>
1415
</head>
1516
<body>
@@ -39,6 +40,7 @@
3940
</main>
4041
{%block footer%}
4142
{%endblock%}
42-
</body>
43-
{%block post_body%}{%endblock%}
43+
</body>
44+
{%block post_body%}{%endblock%}
45+
{%plotly_footer%}
4446
</html>

django_plotly_dash/dash_wrapper.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ def __init__(self,
237237
self._replacements = dict()
238238
self._use_dash_layout = len(self._replacements) < 1
239239

240+
self._return_embedded = False
241+
240242
def use_dash_dispatch(self):
241243
'Indicate if dispatch is using underlying dash code or the wrapped code'
242244
return self._dash_dispatch
@@ -457,8 +459,44 @@ def extra_html_properties(self, prefix=None, postfix=None, template_type=None):
457459
'prefix':prefix,
458460
}
459461

462+
def index(self, *args, **kwargs): # pylint: disable=unused-argument
463+
scripts = self._generate_scripts_html()
464+
css = self._generate_css_dist_html()
465+
config = self._generate_config_html()
466+
metas = self._generate_meta_html()
467+
title = getattr(self, 'title', 'Dash')
468+
if self._favicon:
469+
favicon = '<link rel="icon" type="image/x-icon" href="{}">'.format(
470+
flask.url_for('assets.static', filename=self._favicon))
471+
else:
472+
favicon = ''
473+
474+
_app_entry = '''
475+
<div id="react-entry-point">
476+
<div class="_dash-loading">
477+
Loading Django-Plotly-Dash app
478+
</div>
479+
</div>
480+
'''
481+
index = self.interpolate_index(
482+
metas=metas, title=title, css=css, config=config,
483+
scripts=scripts, app_entry=_app_entry, favicon=favicon)
484+
485+
return index
486+
460487
def interpolate_index(self, **kwargs):
461-
resp = super(WrappedDash, self).interpolate_index(**kwargs)
462488

463-
#print(resp)
464-
return resp
489+
if not self._return_embedded:
490+
resp = super(WrappedDash, self).interpolate_index(**kwargs)
491+
return resp
492+
493+
self._return_embedded.add_css(kwargs['css'])
494+
self._return_embedded.add_config(kwargs['config'])
495+
self._return_embedded.add_scripts(kwargs['scripts'])
496+
497+
return kwargs['app_entry']
498+
499+
def set_embedded(self, embedded_holder=None):
500+
self._return_embedded = embedded_holder if embedded_holder else EmbeddedHolder()
501+
def exit_embedded(self):
502+
self._return_embedded = False

django_plotly_dash/middleware.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
'''
2+
Django-plotly-dash middleware
3+
4+
This middleware enables the collection of items from templates for inclusion in the header and footer
5+
'''
6+
7+
class EmbeddedHolder(object):
8+
def __init__(self):
9+
self.css = ""
10+
self.config = ""
11+
self.scripts = ""
12+
def add_css(self, css):
13+
if css:
14+
self.css = css
15+
def add_config(self, config):
16+
if config:
17+
self.config = config
18+
def add_scripts(self, scripts):
19+
if scripts:
20+
self.scripts = scripts
21+
22+
class ContentCollector:
23+
def __init__(self):
24+
self.header_placeholder = "DJANGO_PLOTLY_DASH_HEADER_PLACEHOLDER"
25+
self.footer_placeholder = "DJANGO_PLOTLY_DASH_FOOTER_PLACEHOLDER"
26+
27+
self.embedded_holder = EmbeddedHolder()
28+
self._encoding = "utf-8"
29+
30+
def adjust_response(self, response):
31+
32+
c1 = self._replace(response.content,
33+
self.header_placeholder,
34+
self.embedded_holder.css)
35+
36+
response.content = self._replace(c1,
37+
self.footer_placeholder,
38+
"\n".join([self.embedded_holder.config,
39+
self.embedded_holder.scripts]))
40+
41+
return response
42+
43+
def _replace(self, content, placeholder, substitution):
44+
return content.replace(self._encode(placeholder),
45+
self._encode(substitution if substitution else ""))
46+
47+
def _encode(self, string):
48+
return string.encode(self._encoding)
49+
50+
class BaseMiddleware:
51+
52+
def __init__(self, get_response):
53+
self.get_response = get_response
54+
55+
def __call__(self, request):
56+
57+
request.dpd_content_handler = ContentCollector()
58+
response = self.get_response(request)
59+
response = request.dpd_content_handler.adjust_response(response)
60+
61+
return response
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div class="django-plotly-dash django-plotly-dash-direct">
2+
{%autoescape off%}{{resp}}{%endautoescape%}
3+
</div>

django_plotly_dash/templatetags/plotly_dash.py

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,21 @@
3636

3737
ws_default_url = "/%s" % pipe_ws_endpoint_name()
3838

39+
def _locate_daapp(name, slug, da, cache_id=None):
40+
41+
app = None
42+
43+
if name is not None:
44+
da, app = DashApp.locate_item(name, stateless=True, cache_id=cache_id)
45+
46+
if slug is not None:
47+
da, app = DashApp.locate_item(slug, stateless=False, cache_id=cache_id)
48+
49+
if not app:
50+
app = da.as_dash_instance()
51+
52+
return da, app
53+
3954
@register.inclusion_tag("django_plotly_dash/plotly_app.html", takes_context=True)
4055
def plotly_app(context, name=None, slug=None, da=None, ratio=0.1, use_frameborder=False, initial_arguments=None):
4156
'Insert a dash application using a html iframe'
@@ -57,8 +72,6 @@ def plotly_app(context, name=None, slug=None, da=None, ratio=0.1, use_frameborde
5772
height: 100%;
5873
"""
5974

60-
app = None
61-
6275
if initial_arguments:
6376
# Generate a cache id
6477
cache_id = "dpd-initial-args-%s" % str(uuid.uuid4()).replace('-', '')
@@ -67,14 +80,35 @@ def plotly_app(context, name=None, slug=None, da=None, ratio=0.1, use_frameborde
6780
else:
6881
cache_id = None
6982

70-
if name is not None:
71-
da, app = DashApp.locate_item(name, stateless=True, cache_id=cache_id)
83+
da, app = _locate_daapp(name, slug, da, cache_id=cache_id)
7284

73-
if slug is not None:
74-
da, app = DashApp.locate_item(slug, stateless=False, cache_id=cache_id)
85+
return locals()
7586

76-
if not app:
77-
app = da.as_dash_instance(cache_id=cache_id)
87+
@register.simple_tag(takes_context=True)
88+
def plotly_header(context):
89+
'Insert placeholder for django-plotly-dash header content'
90+
return context.request.dpd_content_handler.header_placeholder
91+
92+
@register.simple_tag(takes_context=True)
93+
def plotly_footer(context):
94+
'Insert placeholder for django-plotly-dash footer content'
95+
return context.request.dpd_content_handler.footer_placeholder
96+
97+
@register.inclusion_tag("django_plotly_dash/plotly_direct.html", takes_context=True)
98+
def plotly_direct(context, name=None, slug=None, da=None):
99+
'Direct insertion of a Dash app'
100+
101+
da, app = _locate_daapp(name, slug, da)
102+
103+
view_func = app.locate_endpoint_function()
104+
105+
# Load embedded holder inserted by middleware
106+
eh = context.request.dpd_content_handler.embedded_holder
107+
app.set_embedded(eh)
108+
try:
109+
resp = view_func()
110+
finally:
111+
app.exit_embedded()
78112

79113
return locals()
80114

@@ -87,14 +121,8 @@ def plotly_message_pipe(context, url=None):
87121
@register.simple_tag()
88122
def plotly_app_identifier(name=None, slug=None, da=None, postfix=None):
89123
'Return a slug-friendly identifier'
90-
if name is not None:
91-
da, app = DashApp.locate_item(name, stateless=True)
92-
93-
if slug is not None:
94-
da, app = DashApp.locate_item(slug, stateless=False)
95124

96-
if not app:
97-
app = da.as_dash_instance()
125+
da, app = _locate_daapp(name, slug, da)
98126

99127
slugified_id = app.slugified_id()
100128

@@ -106,14 +134,7 @@ def plotly_app_identifier(name=None, slug=None, da=None, postfix=None):
106134
def plotly_class(name=None, slug=None, da=None, prefix=None, postfix=None, template_type=None):
107135
'Return a string of space-separated class names'
108136

109-
if name is not None:
110-
da, app = DashApp.locate_item(name, stateless=True)
111-
112-
if slug is not None:
113-
da, app = DashApp.locate_item(slug, stateless=False)
114-
115-
if not app:
116-
app = da.as_dash_instance()
137+
da, app = _locate_daapp(name, slug, da)
117138

118139
return app.extra_html_properties(prefix=prefix,
119140
postfix=postfix,

0 commit comments

Comments
 (0)