diff --git a/demo/demo/dash_apps.py b/demo/demo/dash_apps.py new file mode 100644 index 00000000..e8a5a55a --- /dev/null +++ b/demo/demo/dash_apps.py @@ -0,0 +1,102 @@ +'''Dash demonstration application + +TODO attribution here +''' + +# The linter doesn't like the members of the html and dcc imports (as they are dynamic?) +#pylint: disable=no-member + +import dash +import dash_core_components as dcc +import dash_html_components as html +import plotly.graph_objs as go +#import dpd_components as dpd +import numpy as np +from django_plotly_dash import DjangoDash + +#from .urls import app_name +app_name = "DPD demo application" + +dashboard_name1 = 'dash_example_1' +dash_example1 = DjangoDash(name=dashboard_name1, + serve_locally=True, + app_name=app_name + ) + +# Below is a random Dash app. +# I encountered no major problems in using Dash this way. I did encounter problems but it was because +# I was using e.g. Bootstrap inconsistenyly across the dash layout. Staying consistent worked fine for me. +dash_example1.layout = html.Div(id='main', + children=[ + html.Div([dcc.Dropdown(id='my-dropdown1', + options=[{'label': 'New York City', 'value': 'NYC'}, + {'label': 'Montreal', 'value': 'MTL'}, + {'label': 'San Francisco', 'value': 'SF'} + ], + value='NYC', + className='col-md-12', + ), + html.Div(id='test-output-div') + ]), + + dcc.Dropdown( + id='my-dropdown2', + options=[ + {'label': 'Oranges', 'value': 'Oranges'}, + {'label': 'Plums', 'value': 'Plums'}, + {'label': 'Peaches', 'value': 'Peaches'} + ], + value='Oranges', + className='col-md-12', + ), + + html.Div(id='test-output-div2') + + ]) # end of 'main' + +@dash_example1.expanded_callback( + dash.dependencies.Output('test-output-div', 'children'), + [dash.dependencies.Input('my-dropdown1', 'value')]) +def callback_test(*args, **kwargs): #pylint: disable=unused-argument + 'Callback to generate test data on each change of the dropdown' + + # Creating a random Graph from a Plotly example: + N = 500 + random_x = np.linspace(0, 1, N) + random_y = np.random.randn(N) + + # Create a trace + trace = go.Scatter(x=random_x, + y=random_y) + + data = [trace] + + layout = dict(title='', + yaxis=dict(zeroline=False, title='Total Expense (£)',), + xaxis=dict(zeroline=False, title='Date', tickangle=0), + margin=dict(t=20, b=50, l=50, r=40), + height=350, + ) + + + fig = dict(data=data, layout=layout) + line_graph = dcc.Graph(id='line-area-graph2', figure=fig, style={'display':'inline-block', 'width':'100%', + 'height':'100%;'}) + children = [line_graph] + + return children + + +@dash_example1.expanded_callback( + dash.dependencies.Output('test-output-div2', 'children'), + [dash.dependencies.Input('my-dropdown2', 'value')]) +def callback_test2(*args, **kwargs): + 'Callback to exercise session functionality' + + print(args) + print(kwargs) + + children = [html.Div(["You have selected %s." %(args[0])]), + html.Div(["The session context message is '%s'" %(kwargs['session_state']['django_to_dash_context'])])] + + return children diff --git a/demo/demo/plotly_apps.py b/demo/demo/plotly_apps.py index 68ed4538..9ca73464 100644 --- a/demo/demo/plotly_apps.py +++ b/demo/demo/plotly_apps.py @@ -117,7 +117,7 @@ def callback_c(*args, **kwargs): return "Args are [%s] and kwargs are %s" %(",".join(args), str(kwargs)) liveIn = DjangoDash("LiveInput", - serve_locally=True, + serve_locally=False, add_bootstrap_links=True) liveIn.layout = html.Div([ diff --git a/demo/demo/settings.py b/demo/demo/settings.py index 6aeb2005..9871f2b6 100644 --- a/demo/demo/settings.py +++ b/demo/demo/settings.py @@ -51,6 +51,9 @@ 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', + + 'django_plotly_dash.middleware.BaseMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] diff --git a/demo/demo/templates/base.html b/demo/demo/templates/base.html index a3bb833b..cba3e48e 100644 --- a/demo/demo/templates/base.html +++ b/demo/demo/templates/base.html @@ -1,16 +1,17 @@ - {%load plotly_dash%} - {%load staticfiles%} - {%load bootstrap4%} - {%bootstrap_css%} - {%bootstrap_javascript jquery="full"%} - {%block extra_header%}{%endblock%} - {%block app_header_css%} + {% load plotly_dash%} + {% load staticfiles%} + {% load bootstrap4%} + {% bootstrap_css%} + {% bootstrap_javascript jquery="full"%} + {% block extra_header%}{% endblock%} + {% block app_header_css%} - {%endblock%} - Django Plotly Dash Examples - {%block title%}{%endblock%} + {% endblock%} + {% plotly_header%} + Django Plotly Dash Examples - {% block title%}{% endblock%}
@@ -19,26 +20,33 @@ Logo - {%block demo_items%} + {% block demo_items %} Contents - Demo One - Simple Use - Demo Two - Initial State - Demo Three - Enhanced Callbacks - Demo Four - Live Updating + {% block demo_menu_items %} + {% if 0 %} + One - Simple Use + Two - Initial State + Three - Enhanced Callbacks + Four - Live Updating + Five - Direct Injection + Six - Simple Injection + {% endif %} + {% endblock %} Online Documentation - {%endblock%} + {% endblock %}
- {%block content%}{%endblock%} + {% block content%}{% endblock%}
- {%block footer%} - {%endblock%} - -{%block post_body%}{%endblock%} + {% block footer%} + {% endblock%} + + {% block post_body%}{% endblock%} + {% plotly_footer%} diff --git a/demo/demo/templates/demo_five.html b/demo/demo/templates/demo_five.html new file mode 100644 index 00000000..077944db --- /dev/null +++ b/demo/demo/templates/demo_five.html @@ -0,0 +1,35 @@ +{%extends "base.html"%} +{%load plotly_dash%} + +{%block title%}Demo One - Simple Embedding{%endblock%} + +{%block content%} +

Direct App Embedding

+

+ This is a simple example of use of a dash application within a Django template. Use of + the plotly_direct template tag with the name of a dash application causes + the Dash application to be directly embedded within the page. +

+

+ The plotly_class tag is also used to wrap the application in css class names based on the + application (django-plotly-dash), the + type of the embedding (here labelled "div-direct"), and the slugified version of the app name (simpleexample). +

+
+
+

{% load plotly_dash %}

+

<div class="{% plotly_class name="SimpleExample" template_type="div-direct"%}"> +

{% plotly_direct name="SimpleExample" %}

+

<\div> +

+
+

+
+
+
+ {%plotly_direct name="SimpleExample"%} +
+
+
+

+{%endblock%} diff --git a/demo/demo/templates/demo_four.html b/demo/demo/templates/demo_four.html index c6e1ace4..a118e5b2 100644 --- a/demo/demo/templates/demo_four.html +++ b/demo/demo/templates/demo_four.html @@ -11,6 +11,18 @@

Live Updating

Live updating uses a websocket connection. The server pushes messages to the UI, and this is then translated into a callback through a dash component.

+

+Each press of a button causes a new random value to be added to that colour's time series in each +chart. Separate values are generated for each chart. The top chart has values +local to this page, and the bottom chart - including its values - is shared across all views of this +page. +

+

+Reloading this page, or viewing in a second browser window, will show a new and initially empty +top chart and a copy of the bottom chart. Pressing any button in any window will cause all instances +of the bottom chart to update with the same values. Note that button presses are throttled so that +only one press per colour per second is processed. +

{% load plotly_dash %}

@@ -45,7 +57,7 @@

Live Updating

Any http command - can be used to send a message to the apps. This is equiavent to a press of + can be used to send a message to the apps. This is equivalent to a press of the red button. Other colours can be specified, including yellow, cyan and black in addition to the three named in the LiveInput app.

diff --git a/demo/demo/templates/demo_six.html b/demo/demo/templates/demo_six.html new file mode 100644 index 00000000..8563cad9 --- /dev/null +++ b/demo/demo/templates/demo_six.html @@ -0,0 +1,36 @@ +{%extends "base.html"%} +{%load plotly_dash%} + +{%block title%}Demo Six - Simple Injection{%endblock%} + +{%block content%} + +

Simple embedding

+ +

+Direct insertion of html into a web page. +

+ +

+This demo is based on a contribution by, and +with thanks to, @eddy-ojb +

+ +
+
+

{% load plotly_dash %}

+

<div class="{% plotly_class name="dash_example_1" template_type="div-direct"%}"> +

{% plotly_direct name="dash_example_1" %}

+

<\div> +

+
+

+
+
+
+ {%plotly_direct name="dash_example_1"%} +
+
+
+

+{%endblock%} diff --git a/demo/demo/templates/index.html b/demo/demo/templates/index.html index 8c17c5cc..921a6b06 100644 --- a/demo/demo/templates/index.html +++ b/demo/demo/templates/index.html @@ -11,5 +11,7 @@

Demonstration Application

  • Demo Two - storage of application initial state within Django
  • Demo Three - adding Django features with enhanced callbacks
  • Demo Four - live updating of apps by pushing from the Django server
  • +
  • Demo Five - injection of a Dash application without embedding in an html iframe
  • +
  • Demo Six - simple html injection example
  • {%endblock%} diff --git a/demo/demo/tests/test_dpd_demo.py b/demo/demo/tests/test_dpd_demo.py index 23b89239..d1d7227d 100644 --- a/demo/demo/tests/test_dpd_demo.py +++ b/demo/demo/tests/test_dpd_demo.py @@ -10,7 +10,7 @@ def test_template_tag_use(client): 'Check use of template tag' - for name in ['demo-one', 'demo-two', 'demo-three', 'demo-four',]: + for name in ['demo-one', 'demo-two', 'demo-three', 'demo-four', 'demo-five', 'demo-six',]: url = reverse(name, kwargs={}) response = client.get(url) diff --git a/demo/demo/urls.py b/demo/demo/urls.py index 1dff0780..c6f9d4dd 100644 --- a/demo/demo/urls.py +++ b/demo/demo/urls.py @@ -27,15 +27,20 @@ # Load demo plotly apps - this triggers their registration import demo.plotly_apps # pylint: disable=unused-import +import demo.dash_apps # pylint: disable=unused-import from django_plotly_dash.views import add_to_session +from .views import dash_example_1_view + urlpatterns = [ url('^$', TemplateView.as_view(template_name='index.html'), name="home"), url('^demo-one$', TemplateView.as_view(template_name='demo_one.html'), name="demo-one"), url('^demo-two$', TemplateView.as_view(template_name='demo_two.html'), name="demo-two"), url('^demo-three$', TemplateView.as_view(template_name='demo_three.html'), name="demo-three"), url('^demo-four$', TemplateView.as_view(template_name='demo_four.html'), name="demo-four"), + 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('^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 new file mode 100644 index 00000000..b73a2f1f --- /dev/null +++ b/demo/demo/views.py @@ -0,0 +1,19 @@ +''' +Example view generating non-trivial content +''' + +from django.shortcuts import render + +#pylint: disable=unused-argument + +def dash_example_1_view(request, template_name="demo_six.html", **kwargs): + 'Example view that inserts content into the dash context passed to the dash application' + + context = {} + + # create some context to send over to Dash: + dash_context = request.session.get("django_plotly_dash", dict()) + dash_context['django_to_dash_context'] = "I am Dash receiving context from Django" + request.session['django_plotly_dash'] = dash_context + + return render(request, template_name=template_name, context=context) diff --git a/django_plotly_dash/dash_wrapper.py b/django_plotly_dash/dash_wrapper.py index 0be40a20..13355ed1 100644 --- a/django_plotly_dash/dash_wrapper.py +++ b/django_plotly_dash/dash_wrapper.py @@ -36,6 +36,8 @@ from plotly.utils import PlotlyJSONEncoder from .app_name import app_name, main_view_label +from .middleware import EmbeddedHolder + uid_counter = 0 @@ -237,6 +239,8 @@ def __init__(self, self._replacements = dict() self._use_dash_layout = len(self._replacements) < 1 + self._return_embedded = False + def use_dash_dispatch(self): 'Indicate if dispatch is using underlying dash code or the wrapped code' return self._dash_dispatch @@ -456,3 +460,49 @@ def extra_html_properties(self, prefix=None, postfix=None, template_type=None): 'template_type':template_type, 'prefix':prefix, } + + def index(self, *args, **kwargs): # pylint: disable=unused-argument + scripts = self._generate_scripts_html() + css = self._generate_css_dist_html() + config = self._generate_config_html() + metas = self._generate_meta_html() + title = getattr(self, 'title', 'Dash') + if self._favicon: + import flask + favicon = ''.format( + flask.url_for('assets.static', filename=self._favicon)) + else: + favicon = '' + + _app_entry = ''' +
    +
    + Loading Django-Plotly-Dash app +
    +
    +''' + index = self.interpolate_index( + metas=metas, title=title, css=css, config=config, + scripts=scripts, app_entry=_app_entry, favicon=favicon) + + return index + + def interpolate_index(self, **kwargs): #pylint: disable=arguments-differ + + if not self._return_embedded: + resp = super(WrappedDash, self).interpolate_index(**kwargs) + return resp + + self._return_embedded.add_css(kwargs['css']) + self._return_embedded.add_config(kwargs['config']) + self._return_embedded.add_scripts(kwargs['scripts']) + + return kwargs['app_entry'] + + def set_embedded(self, embedded_holder=None): + 'Set a handler for embedded references prior to evaluating a view function' + self._return_embedded = embedded_holder if embedded_holder else EmbeddedHolder() + + def exit_embedded(self): + 'Exit the embedded section after processing a view' + self._return_embedded = False diff --git a/django_plotly_dash/middleware.py b/django_plotly_dash/middleware.py new file mode 100644 index 00000000..75fede53 --- /dev/null +++ b/django_plotly_dash/middleware.py @@ -0,0 +1,75 @@ +''' +Django-plotly-dash middleware + +This middleware enables the collection of items from templates for inclusion in the header and footer +''' + +#pylint: disable=too-few-public-methods + +class EmbeddedHolder(object): + 'Hold details of embedded content from processing a view' + def __init__(self): + self.css = "" + self.config = "" + self.scripts = "" + def add_css(self, css): + 'Add css content' + if css: + self.css = css + def add_config(self, config): + 'Add config content' + if config: + self.config = config + def add_scripts(self, scripts): + 'Add js content' + if scripts: + self.scripts = scripts + +class ContentCollector: + ''' + Collect content during view processing, and substitute in response by finding magic strings. + + This enables view functionality, such as template tags, to introduce content such as css and js + inclusion into the header and footer. + ''' + def __init__(self): + self.header_placeholder = "DJANGO_PLOTLY_DASH_HEADER_PLACEHOLDER" + self.footer_placeholder = "DJANGO_PLOTLY_DASH_FOOTER_PLACEHOLDER" + + self.embedded_holder = EmbeddedHolder() + self._encoding = "utf-8" + + def adjust_response(self, response): + 'Locate placeholder magic strings and replace with content' + + c1 = self._replace(response.content, + self.header_placeholder, + self.embedded_holder.css) + + response.content = self._replace(c1, + self.footer_placeholder, + "\n".join([self.embedded_holder.config, + self.embedded_holder.scripts])) + + return response + + def _replace(self, content, placeholder, substitution): + return content.replace(self._encode(placeholder), + self._encode(substitution if substitution else "")) + + def _encode(self, string): + return string.encode(self._encoding) + +class BaseMiddleware: + 'Django-plotly-dash middleware' + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + + request.dpd_content_handler = ContentCollector() + response = self.get_response(request) + response = request.dpd_content_handler.adjust_response(response) + + return response diff --git a/django_plotly_dash/templates/django_plotly_dash/plotly_direct.html b/django_plotly_dash/templates/django_plotly_dash/plotly_direct.html new file mode 100644 index 00000000..284ea601 --- /dev/null +++ b/django_plotly_dash/templates/django_plotly_dash/plotly_direct.html @@ -0,0 +1,3 @@ +
    + {%autoescape off%}{{resp}}{%endautoescape%} +
    diff --git a/django_plotly_dash/templatetags/plotly_dash.py b/django_plotly_dash/templatetags/plotly_dash.py index e92482ab..d74df8a6 100644 --- a/django_plotly_dash/templatetags/plotly_dash.py +++ b/django_plotly_dash/templatetags/plotly_dash.py @@ -30,12 +30,27 @@ from django.core.cache import cache from django_plotly_dash.models import DashApp -from django_plotly_dash.util import pipe_ws_endpoint_name +from django_plotly_dash.util import pipe_ws_endpoint_name, cache_timeout_initial_arguments register = template.Library() ws_default_url = "/%s" % pipe_ws_endpoint_name() +def _locate_daapp(name, slug, da, cache_id=None): + + app = None + + if name is not None: + da, app = DashApp.locate_item(name, stateless=True, cache_id=cache_id) + + if slug is not None: + da, app = DashApp.locate_item(slug, stateless=False, cache_id=cache_id) + + if not app: + app = da.as_dash_instance() + + return da, app + @register.inclusion_tag("django_plotly_dash/plotly_app.html", takes_context=True) def plotly_app(context, name=None, slug=None, da=None, ratio=0.1, use_frameborder=False, initial_arguments=None): 'Insert a dash application using a html iframe' @@ -57,24 +72,43 @@ def plotly_app(context, name=None, slug=None, da=None, ratio=0.1, use_frameborde height: 100%; """ - app = None - if initial_arguments: # Generate a cache id cache_id = "dpd-initial-args-%s" % str(uuid.uuid4()).replace('-', '') # Store args in json form in cache - cache.set(cache_id, initial_arguments, 60) + cache.set(cache_id, initial_arguments, cache_timeout_initial_arguments()) else: cache_id = None - if name is not None: - da, app = DashApp.locate_item(name, stateless=True, cache_id=cache_id) + da, app = _locate_daapp(name, slug, da, cache_id=cache_id) - if slug is not None: - da, app = DashApp.locate_item(slug, stateless=False, cache_id=cache_id) + return locals() - if not app: - app = da.as_dash_instance(cache_id=cache_id) +@register.simple_tag(takes_context=True) +def plotly_header(context): + 'Insert placeholder for django-plotly-dash header content' + return context.request.dpd_content_handler.header_placeholder + +@register.simple_tag(takes_context=True) +def plotly_footer(context): + 'Insert placeholder for django-plotly-dash footer content' + return context.request.dpd_content_handler.footer_placeholder + +@register.inclusion_tag("django_plotly_dash/plotly_direct.html", takes_context=True) +def plotly_direct(context, name=None, slug=None, da=None): + 'Direct insertion of a Dash app' + + da, app = _locate_daapp(name, slug, da) + + view_func = app.locate_endpoint_function() + + # Load embedded holder inserted by middleware + eh = context.request.dpd_content_handler.embedded_holder + app.set_embedded(eh) + try: + resp = view_func() + finally: + app.exit_embedded() return locals() @@ -87,14 +121,8 @@ def plotly_message_pipe(context, url=None): @register.simple_tag() def plotly_app_identifier(name=None, slug=None, da=None, postfix=None): 'Return a slug-friendly identifier' - if name is not None: - da, app = DashApp.locate_item(name, stateless=True) - - if slug is not None: - da, app = DashApp.locate_item(slug, stateless=False) - if not app: - app = da.as_dash_instance() + da, app = _locate_daapp(name, slug, da) slugified_id = app.slugified_id() @@ -106,14 +134,7 @@ def plotly_app_identifier(name=None, slug=None, da=None, postfix=None): def plotly_class(name=None, slug=None, da=None, prefix=None, postfix=None, template_type=None): 'Return a string of space-separated class names' - if name is not None: - da, app = DashApp.locate_item(name, stateless=True) - - if slug is not None: - da, app = DashApp.locate_item(slug, stateless=False) - - if not app: - app = da.as_dash_instance() + da, app = _locate_daapp(name, slug, da) return app.extra_html_properties(prefix=prefix, postfix=postfix, diff --git a/django_plotly_dash/tests.py b/django_plotly_dash/tests.py index 737598fe..c77a2589 100644 --- a/django_plotly_dash/tests.py +++ b/django_plotly_dash/tests.py @@ -29,6 +29,8 @@ import pytest +#pylint: disable=bare-except + def test_dash_app(): 'Test the import and formation of the dash app orm wrappers' @@ -119,3 +121,85 @@ def test_updating(client): assert response.content == b'{"response": {"props": {"children": "The chosen T-shirt is a medium blue one."}}}' assert response.status_code == 200 + +@pytest.mark.django_db +def test_injection_app_access(client): + 'Check direct use of a stateless application using demo test data' + + from django.urls import reverse + from .app_name import main_view_label + + for route_name in ['layout', 'dependencies', main_view_label]: + for prefix, arg_map in [('app-', {'ident':'dash_example_1'}), + #('', {'ident':'simpleexample-1'}), + ]: + url = reverse('the_django_plotly_dash:%s%s' % (prefix, route_name), kwargs=arg_map) + + response = client.get(url) + + assert response.content + assert response.status_code == 200 + + for route_name in ['routes',]: + for prefix, arg_map in [('app-', {'ident':'dash_example_1'}),]: + url = reverse('the_django_plotly_dash:%s%s' % (prefix, route_name), kwargs=arg_map) + + did_fail = False + try: + response = client.get(url) + except: + did_fail = True + + assert did_fail + +@pytest.mark.django_db +def test_injection_updating(client): + 'Check updating of an app using demo test data' + + import json + from django.urls import reverse + + route_name = 'update-component' + + for prefix, arg_map in [('app-', {'ident':'dash_example_1'}),]: + url = reverse('the_django_plotly_dash:%s%s' % (prefix, route_name), kwargs=arg_map) + + response = client.post(url, json.dumps({'output':{'id':'test-output-div', 'property':'children'}, + 'inputs':[{'id':'my-dropdown1', + 'property':'value', + 'value':'TestIt'}, + ]}), content_type="application/json") + + rStart = b'{"response": {"props": {"children":' + + assert response.content[:len(rStart)] == rStart + assert response.status_code == 200 + + have_thrown = False + + try: + client.post(url, json.dumps({'output':{'id':'test-output-div2', 'property':'children'}, + 'inputs':[{'id':'my-dropdown2', + 'property':'value', + 'value':'TestIt'}, + ]}), content_type="application/json") + except: + have_thrown = True + + assert have_thrown + + session = client.session + session['django_plotly_dash'] = {'django_to_dash_context': 'Test 789 content'} + session.save() + + response3 = client.post(url, json.dumps({'output':{'id':'test-output-div2', 'property':'children'}, + 'inputs':[{'id':'my-dropdown2', + 'property':'value', + 'value':'TestIt'}, + ]}), content_type="application/json") + rStart3 = b'{"response": {"props": {"children":' + + assert response3.content[:len(rStart3)] == rStart3 + assert response3.status_code == 200 + + assert response3.content.find(b'Test 789 content') > 0 diff --git a/django_plotly_dash/util.py b/django_plotly_dash/util.py index a71c6682..bf16df7e 100644 --- a/django_plotly_dash/util.py +++ b/django_plotly_dash/util.py @@ -53,3 +53,7 @@ def insert_demo_migrations(): def http_poke_endpoint_enabled(): 'Return true if the http endpoint is enabled through the settings' return _get_settings().get('http_poke_enabled', True) + +def cache_timeout_initial_arguments(): + 'Return cache timeout, in seconds, for initial arguments' + return _get_settings().get('cache_timeout_initial_arguments', 60) diff --git a/docs/configuration.rst b/docs/configuration.rst index 96da2987..f684ece8 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -21,6 +21,9 @@ below. # Insert data for the demo when migrating "insert_demo_migrations" : False, + + # Timeout for caching of initial arguments in seconds + "cache_timeout_initial_arguments": 60, } Defaults are inserted for missing values. It is also permissible to not have any ``PLOTLY_DASH`` entry in diff --git a/docs/template_tags.rst b/docs/template_tags.rst index 25ad0808..c15f584a 100644 --- a/docs/template_tags.rst +++ b/docs/template_tags.rst @@ -42,6 +42,32 @@ a JSON-encoded string representation. Each entry in the dictionary has the ``id` value is a dictionary mapping property name keys to initial values. +.. _plotly_direct: + +The ``plotly_direct`` template tag +---------------------------------- + +This template tag allows the direct insertion of html into a template, instead +of embedding it in an iframe. + +.. code-block:: jinja + + {%load plotly_dash%} + + {%plotly_direct name="SimpleExample"%} + +The tag arguments are: + +:name = None: The name of the application, as passed to a ``DjangoDash`` constructor. +:slug = None: The slug of an existing ``DashApp`` instance. +:da = None: An existing ``django_plotly_dash.models.DashApp`` model instance. + +These arguments are equivalent to the same ones for the ``plotly_app`` template tag. Note +that ``initial_arguments`` are not currently supported, and as the app is directly injected into +the page there are no arguments to control the size of the iframe. + +This tag should not appear more than once on a page. This rule however is not enforced at present. + .. _plotly_message_pipe: The ``plotly_message_pipe`` template tag