Skip to content

Commit a14df5f

Browse files
committed
Changes to static file handling to facilitate customization. In particular, custom static paths now behave the same way that custom template paths do.
1 parent fbd62fa commit a14df5f

File tree

6 files changed

+56
-27
lines changed

6 files changed

+56
-27
lines changed

nbviewer/app.py

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
except ImportError:
4444
from .utils import cached_property
4545

46+
from notebook.base.handlers import FileFindHandler as StaticFileHandler
47+
4648
#-----------------------------------------------------------------------------
4749
# Code
4850
#-----------------------------------------------------------------------------
@@ -122,12 +124,16 @@ def _load_max_cache_uris(self):
122124
max_cache_uris.add('/' + link['target'])
123125
return max_cache_uris
124126

125-
static_path = Unicode(default_value=pjoin(here, 'static')).tag(config=True)
127+
static_path = Unicode(default_value=os.environ.get("NBVIEWER_STATIC_PATH", ""), help="Custom path for loading additional static files.").tag(config=True)
128+
129+
static_url_prefix = Unicode(default_value='/static/').tag(config=True)
126130

127-
static_url_prefix = Unicode().tag(config=True)
128-
@default('static_url_prefix')
131+
# Not exposed to end user for configuration, since needs to access base_url
132+
_static_url_prefix = Unicode()
133+
@default('_static_url_prefix')
129134
def _load_static_url_prefix(self):
130-
return url_path_join(self.base_url, '/static/')
135+
# Last '/' ensures that NBViewer still works regardless of whether user chooses e.g. '/static2/' pr '/static2' as their custom prefix
136+
return url_path_join(self.base_url, self.static_url_prefix, '/')
131137

132138
@cached_property
133139
def base_url(self):
@@ -231,16 +237,26 @@ def rate_limiter(self):
231237
rate_limiter = RateLimiter(limit=options.rate_limit, interval=options.rate_limit_interval, cache=self.cache)
232238
return rate_limiter
233239

240+
@cached_property
241+
def static_paths(self):
242+
default_static_path = pjoin(here, 'static')
243+
if self.static_path:
244+
log.app_log.info("Using custom static path {}".format(self.static_path))
245+
static_paths = [self.static_path, default_static_path]
246+
else:
247+
static_paths = [default_static_path]
248+
return static_paths
249+
234250
@cached_property
235251
def template_paths(self):
236-
template_paths = pjoin(here, 'templates')
252+
default_template_path = pjoin(here, 'templates')
237253
if options.template_path is not None:
238254
log.app_log.info("Using custom template path {}".format(options.template_path))
239-
template_paths = [options.template_path, template_paths]
240-
255+
template_paths = [options.template_path, default_template_path]
256+
else:
257+
template_paths = [default_template_path]
241258
return template_paths
242259

243-
244260
def init_tornado_application(self):
245261
# handle handlers
246262
handler_names = dict(
@@ -270,6 +286,8 @@ def init_tornado_application(self):
270286

271287
# input traitlets to settings
272288
settings = dict(
289+
# Allow FileFindHandler to load static directories from e.g. a Docker container
290+
allow_remote_access=True,
273291
base_url=self.base_url,
274292
binder_base_url=options.binder_base_url,
275293
cache=self.cache,
@@ -303,8 +321,10 @@ def init_tornado_application(self):
303321
providers=options.providers,
304322
rate_limiter=self.rate_limiter,
305323
render_timeout=options.render_timeout,
306-
static_path=self.static_path,
307-
static_url_prefix=self.static_url_prefix,
324+
static_handler_class = StaticFileHandler,
325+
# FileFindHandler expects list of static paths, so self.static_path*s* is correct
326+
static_path=self.static_paths,
327+
static_url_prefix=self._static_url_prefix,
308328
statsd_host=options.statsd_host,
309329
statsd_port=options.statsd_port,
310330
statsd_prefix=options.statsd_prefix,

nbviewer/frontpage.json

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@
99
{
1010
"text": "IPython",
1111
"target": "/github/ipython/ipython/blob/6.x/examples/IPython%20Kernel/Index.ipynb",
12-
"img": "/static/img/example-nb/ipython-thumb.png"
12+
"img": "/img/example-nb/ipython-thumb.png"
1313
},
1414
{
1515
"text": "IRuby",
1616
"target": "/github/SciRuby/sciruby-notebooks/blob/master/getting_started.ipynb",
17-
"img": "/static/img/example-nb/iruby-nb.png"
17+
"img": "/img/example-nb/iruby-nb.png"
1818
},
1919
{
2020
"text": "IJulia",
2121
"target": "/url/jdj.mit.edu/~stevenj/IJulia%20Preview.ipynb",
22-
"img": "/static/img/example-nb/ijulia-preview.png"
22+
"img": "/img/example-nb/ijulia-preview.png"
2323
}
2424
]
2525
},
@@ -29,17 +29,17 @@
2929
{
3030
"text": "Python for Signal Processing",
3131
"target": "/github/unpingco/Python-for-Signal-Processing/",
32-
"img": "/static/img/example-nb/python-signal.png"
32+
"img": "/img/example-nb/python-signal.png"
3333
},
3434
{
3535
"text": "O'Reilly Book",
3636
"target": "/github/ptwobrussell/Mining-the-Social-Web-2nd-Edition/tree/master/ipynb",
37-
"img": "/static/img/example-nb/mining-slice.png"
37+
"img": "/img/example-nb/mining-slice.png"
3838
},
3939
{
4040
"text": "Probabilistic Programming",
4141
"target": "/github/CamDavidsonPilon/Probabilistic-Programming-and-Bayesian-Methods-for-Hackers/blob/master/Chapter1_Introduction/Ch1_Introduction_PyMC3.ipynb",
42-
"img": "/static/img/example-nb/probabilistic-bayesian.png"
42+
"img": "/img/example-nb/probabilistic-bayesian.png"
4343
}
4444
]
4545
},
@@ -49,47 +49,47 @@
4949
{
5050
"text": "Data Visualization with Lightning",
5151
"target": "/github/lightning-viz/lightning-example-notebooks/blob/master/index.ipynb",
52-
"img": "/static/img/example-nb/lightning.png"
52+
"img": "/img/example-nb/lightning.png"
5353
},
5454
{
5555
"text": "Interactive data visualization with Bokeh",
5656
"target": "/github/bokeh/bokeh-notebooks/blob/master/index.ipynb",
57-
"img": "/static/img/example-nb/bokeh.png"
57+
"img": "/img/example-nb/bokeh.png"
5858
},
5959
{
6060
"text": "Interactive plots with Plotly",
6161
"target": "/github/plotly/python-user-guide/blob/master/Index.ipynb",
62-
"img": "/static/img/example-nb/plotly.png"
62+
"img": "/img/example-nb/plotly.png"
6363
},
6464
{
6565
"text": "XKCD Plot With Matplotlib",
6666
"target": "/url/jakevdp.github.com/downloads/notebooks/XKCD_plots.ipynb",
67-
"img": "/static/img/example-nb/XKCD-Matplotlib.png"
67+
"img": "/img/example-nb/XKCD-Matplotlib.png"
6868
},
6969
{
7070
"text": "Python for Vision Research",
7171
"target": "/github/gestaltrevision/python_for_visres/blob/master/index.ipynb",
72-
"img": "/static/img/example-nb/python_for_visres.png"
72+
"img": "/img/example-nb/python_for_visres.png"
7373
},
7474
{
7575
"text": "Non Parametric Regression",
7676
"target": "/gist/fonnesbeck/2352771",
77-
"img": "/static/img/example-nb/covariance.png"
77+
"img": "/img/example-nb/covariance.png"
7878
},
7979
{
8080
"text": "Partial Differential Equations Solver",
8181
"target": "/github/waltherg/notebooks/blob/master/2013-12-03-Crank_Nicolson.ipynb",
82-
"img": "/static/img/example-nb/pde_solver_with_numpy.png"
82+
"img": "/img/example-nb/pde_solver_with_numpy.png"
8383
},
8484
{
8585
"text": "Analysis of current events",
8686
"target": "/gist/darribas/4121857",
87-
"img": "/static/img/example-nb/gaza.png"
87+
"img": "/img/example-nb/gaza.png"
8888
},
8989
{
9090
"text": "Jaynes-Cummings model",
9191
"target": "/github/jrjohansson/qutip-lectures/blob/master/Lecture-1-Jaynes-Cumming-model.ipynb",
92-
"img": "/static/img/example-nb/jaynes-cummings.png"
92+
"img": "/img/example-nb/jaynes-cummings.png"
9393
}
9494
]
9595
}

nbviewer/providers/base.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ def providers(self):
231231
def rate_limiter(self):
232232
return self.settings['rate_limiter']
233233

234+
@property
235+
def static_url_prefix(self):
236+
return self.settings['static_url_prefix']
237+
234238
@property
235239
def statsd(self):
236240
if hasattr(self, '_statsd'):
@@ -286,6 +290,10 @@ def template_namespace(self):
286290
"jupyter_widgets_html_manager_version": self.jupyter_widgets_html_manager_version,
287291
}
288292

293+
# Overwrite the static_url method from Tornado to work better with our custom StaticFileHandler
294+
def static_url(self, url):
295+
return url_path_join(self.static_url_prefix, url)
296+
289297
def breadcrumbs(self, path, base_url):
290298
"""Generate a list of breadcrumbs"""
291299
breadcrumbs = []

nbviewer/templates/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ <h3 class="section-heading">{{section.header}}</h3>
4141
<li class="col-md-4">
4242
<p class="marketing-byline">{{link.text}}</p>
4343
<a class="thumbnail" href="{{ from_base(link.target) }}" target="_blank">
44-
<img src="{{ from_base(link.img) }}" />
44+
<img src="{{ static_url(link.img) }}" />
4545
</a>
4646
</li>
4747
{% endfor %}

nbviewer/templates/layout.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
<![endif]-->
5959

6060
<!-- Le fav and touch icons -->
61-
<link rel="shortcut icon" href="/static/ico/ipynb_icon_16x16.png">
61+
<link rel="shortcut icon" href="{{ static_url("ico/ipynb_icon_16x16.png") }}">
6262
<link rel="apple-touch-icon-precomposed" sizes="144x144"
6363
href="{{ static_url("ico/apple-touch-icon-144-precomposed.png") }}">
6464
<link rel="apple-touch-icon-precomposed" sizes="114x114"

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ elasticsearch
22
jupyter_client
33
markdown>=3.0
44
newrelic!=2.80.0.60
5+
notebook>=5.0
56
nbformat>=4.2
67
nbconvert>=5.4
78
ipython

0 commit comments

Comments
 (0)