Skip to content

Commit

Permalink
Add staticfiles finders for locating asset and component static files (
Browse files Browse the repository at this point in the history
…#99)

* v0.9.5 packages for future reference

* Explicit list of default staticfiles finders

* Added staticfiles finders for local components and assets

* Component staticfiles finder now matches path

* Document additional configuration for serving static files from components
  • Loading branch information
delsim authored and GibbsConsulting committed Jan 10, 2019
1 parent 9b8c9f6 commit 47bb68f
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 27 deletions.
42 changes: 20 additions & 22 deletions demo/demo/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,25 +170,23 @@
},
}

# In order to serve dash components locally - not recommended in general, but
# can be useful for development especially if offline - we add in the root directory
# of each module. This is a bit of fudge and only needed if serve_locally=True is
# set on a DjangoDash instance.
#
# Note that this makes all of the python module (including .py and .pyc) files available
# through the static route. This is not a big deal for development but at the same time
# not particularly neat or tidy.

if DEBUG:

import importlib

for dash_module_name in ['dash_core_components',
'dash_html_components',
'dash_renderer',
'dpd_components',
'dash_bootstrap_components',
]:

module = importlib.import_module(dash_module_name)
STATICFILES_DIRS.append(("dash/%s"%dash_module_name, os.path.dirname(module.__file__)))
# Staticfiles finders for locating dash app assets and related files

STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',

'django_plotly_dash.finders.DashAssetFinder',
'django_plotly_dash.finders.DashComponentFinder',
]

# Plotly components containing static content that should
# be handled by the Django staticfiles infrastructure

PLOTLY_COMPONENTS = [
'dash_core_components',
'dash_html_components',
'dash_bootstrap_components',
'dash_renderer',
'dpd_components',
]
4 changes: 2 additions & 2 deletions demo/demo/templates/demo_seven.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ <h1>Dash Bootstrap Components</h1>
<div class="card-body">
<p><span>{</span>% load plotly_dash %}</p>
<p>&lt;div class="<span>{</span>% plotly_class name="BootstrapApplication"%}">
<p class="ml-3"><span>{</span>% plotly_app name="BootstrapApplication" %}</p>
<p class="ml-3"><span>{</span>% plotly_app name="BootstrapApplication" ratio=0.2 %}</p>
<p>&lt;\div>
</div>
</div>
<p></p>
<div class="card border-dark">
<div class="card-body">
<div class="{%plotly_class name="BootstrapApplication"%}">
{%plotly_app name="BootstrapApplication"%}
{%plotly_app name="BootstrapApplication" ratio=0.2 %}
</div>
</div>
</div>
Expand Down
10 changes: 10 additions & 0 deletions django_plotly_dash/dash_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
'''

import json
import inspect

from dash import Dash
from flask import Flask
Expand All @@ -50,6 +51,10 @@ def add_usable_app(name, app):
usable_apps[name] = app
return name

def all_apps():
'Return a dictionary of all locally registered apps with the slug name as key'
return usable_apps

def get_local_stateless_by_name(name):
'''
Locate a registered dash app by name, and return a DjangoDash instance encapsulating the app.
Expand Down Expand Up @@ -107,6 +112,11 @@ def __init__(self, name=None, serve_locally=False,
bootstrap_source = css_url()['href']
self.css.append_script({'external_url':[bootstrap_source,]})

# Remember some caller info for static files
caller_frame = inspect.stack()[1]
self.caller_module = inspect.getmodule(caller_frame[0])
self.caller_module_location = inspect.getfile(self.caller_module)

def as_dash_instance(self, cache_id=None):
'''
Form a dash instance, for stateless use of this app
Expand Down
144 changes: 144 additions & 0 deletions django_plotly_dash/finders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
'''
Staticfiles finders for Dash assets
Copyright (c) 2018 Gibbs Consulting and others - see CONTRIBUTIONS.md
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
'''

import os
import importlib

from collections import OrderedDict

from django.contrib.staticfiles.finders import BaseFinder
from django.contrib.staticfiles.utils import get_files

from django.core.files.storage import FileSystemStorage

from django.conf import settings
from django.apps import apps

from django_plotly_dash.dash_wrapper import all_apps

class DashComponentFinder(BaseFinder):
'Find static files in components'

def __init__(self):

self.locations = []
self.storages = OrderedDict()
self.components = {}

self.ignore_patterns = ["*.py", "*.pyc",]

for component_name in settings.PLOTLY_COMPONENTS:

module = importlib.import_module(component_name)
path_directory = os.path.dirname(module.__file__)

root = path_directory
storage = FileSystemStorage(location=root)
path = "dash/component/%s" % component_name

# Path_directory is where from
# path is desired url mount point of the content of path_directory
# component_name is the name of the component

storage.prefix = path

self.locations.append(component_name)

self.storages[component_name] = storage
self.components[path] = component_name

super(DashComponentFinder, self).__init__()

def find(self, path, all=False):
matches = []
for component_name in self.locations:
storage = self.storages[component_name]
location = storage.location # dir on disc

component_path = "dash/component/%s" % component_name
if len(path) > len(component_path) and path[:len(component_path)] == component_path:

matched_path = os.path.join(location, path[len(component_path)+1:])
if os.path.exists(matched_path):
if not all:
return matched_path
matches.append(matched_path)

return matches

def find_location(self, path):
if os.path.exists(path):
return path

def list(self, ignore_patterns):
for component_name in self.locations:
storage = self.storages[component_name]
for path in get_files(storage, ignore_patterns + self.ignore_patterns):
print("DashAssetFinder",path,storage)
yield path, storage

class DashAssetFinder(BaseFinder):
'Find static files in asset directories'

def __init__(self):

# Get all registered apps

import demo.plotly_apps
import demo.dash_apps

self.apps = all_apps()

self.subdir = 'assets'

self.locations = []
self.storages = OrderedDict()

self.ignore_patterns = ["*.py", "*.pyc",]

for app_slug, obj in self.apps.items():
caller_module = obj.caller_module
location = obj.caller_module_location
path_directory = os.path.join(os.path.dirname(location),self.subdir)

if os.path.isdir(path_directory):

component_name = app_slug
storage = FileSystemStorage(location=path_directory)
path = "dash/assets/%s" % component_name
storage.prefix = path

self.locations.append(component_name)
self.storages[component_name] = storage

super(DashAssetFinder, self).__init__()

def find(self, path, all=False):
return []

def list(self, ignore_patterns):
for component_name in self.locations:
storage = self.storages[component_name]
for path in get_files(storage, ignore_patterns + self.ignore_patterns):
yield path, storage
18 changes: 15 additions & 3 deletions django_plotly_dash/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,25 @@ def component_suites(request, resource=None, component=None, extra_element="", *
'Return part of a client-side component, served locally for some reason'

get_params = request.GET.urlencode()
if get_params:
redone_url = "/static/dash/%s/%s%s?%s" %(component, extra_element, resource, get_params)
if get_params and False:
redone_url = "/static/dash/component/%s/%s%s?%s" %(component, extra_element, resource, get_params)
else:
redone_url = "/static/dash/%s/%s%s" %(component, extra_element, resource)
redone_url = "/static/dash/component/%s/%s%s" %(component, extra_element, resource)

print("Redirecting to :",redone_url)

return HttpResponseRedirect(redirect_to=redone_url)

def app_assets(request, **kwargs):
'Return a local dash app asset, served up through the Django static framework'
get_params = request.GET.urlencode()
extra_part = ""
if get_params:
redone_url = "/static/dash/assets/%s?%s" %(extra_part, get_params)
else:
redone_url = "/static/dash/assets/%s" % extra_part

return HttpResponseRedirect(redirect_to=redone_url)

# pylint: disable=wrong-import-position, wrong-import-order
from django.template.response import TemplateResponse
Expand Down
39 changes: 39 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,45 @@ below.
Defaults are inserted for missing values. It is also permissible to not have any ``PLOTLY_DASH`` entry in
the Django settings file.

The Django staticfiles infrastructure is used to serve all local static files for
the Dash apps. This requires adding a setting for the specification of additional static
file finders

.. code-block:: python
# Staticfiles finders for locating dash app assets and related files
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'django_plotly_dash.finders.DashAssetFinder',
'django_plotly_dash.finders.DashComponentFinder',
]
and also providing a list of components used

.. code-block:: python
# Plotly components containing static content that should
# be handled by the Django staticfiles infrastructure
PLOTLY_COMPONENTS = [
# Common components
'dash_core_components',
'dash_html_components',
'dash_renderer',
# django-plotly-dash components
'dpd_components',
# Other components, as needed
'dash_bootstrap_components',
]
This list should be extended with any additional components that the applications
use, where the components have files that have to be served locally.

.. _endpoints:

Endpoints
Expand Down
5 changes: 5 additions & 0 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,14 @@ well as adding ``channels`` to the ``INSTALLED_APPS`` list, a ``CHANNEL_LAYERS``

The host and port entries in ``hosts`` should be adjusted to match the network location of the Redis instance.

Further configuration
---------------------

Further configuration options can be specified through the optional ``PLOTLY_DASH`` settings variable. The
available options are detailed in the :ref:`configuration <configuration>` section.

This includes arranging for Dash assets to be served using the Django ``staticfiles`` functionality.

Source code and demo
--------------------

Expand Down

0 comments on commit 47bb68f

Please sign in to comment.