Support index files
ionelmc opened this issue · 12 comments
Eg: if there's an foo/index.html
it should be served at foo/
. Also, foo
should redirect to foo/
.
For me, this is getting too far away from WhiteNoise's original goals to be supported as a core feature. WhiteNoise works best when the files it serves can be cached forever, which means giving each version a unique name like app.ea27bc4.js
. It's not really designed as a generic file server.
However, if you really want this feature you could implement it as a subclass quite easily. Something like this (untested) would probably work:
class WhiteNoiseIndex(WhiteNoise):
def add_files(self, *args, **kwargs):
super(WhiteNoiseIndex, self).add_files(*args, **kwargs)
index_files = {}
for url, static_file in self.files.items():
if url.endswith('/index.html'):
index_files[url[:-len('index.html')]] = static_file
self.files.update(index_files)
For WhiteNoise v2.0+, this needs to be modified slightly, so that it works with autorefresh when developing. (Feature was added in 25f5757).
eg:
from whitenoise.django import DjangoWhiteNoise
class IndexWhiteNoise(DjangoWhiteNoise):
"""Adds support for serving index pages for directory paths."""
INDEX_NAME = 'index.html'
def add_files(self, *args, **kwargs):
super(IndexWhiteNoise, self).add_files(*args, **kwargs)
index_page_suffix = "/" + self.INDEX_NAME
index_name_length = len(self.INDEX_NAME)
index_files = {}
for url, static_file in self.files.items():
# Add an additional fake filename to serve index pages for '/'.
if url.endswith(index_page_suffix):
index_files[url[:-index_name_length]] = static_file
self.files.update(index_files)
def find_file(self, url):
# In debug mode, find_file() is used to serve files directly from the filesystem
# instead of using the list in `self.files`, so we append the index filename so
# that will be served if present.
if url[-1] == '/':
url += self.INDEX_NAME
return super(IndexWhiteNoise, self).find_file(url)
And for WhiteNoise 3+, something like:
from whitenoise.middleware import WhiteNoiseMiddleware
class IndexWhiteNoise(WhiteNoiseMiddleware):
"""Adds support for serving index pages for directory paths."""
INDEX_NAME = 'index.html'
def update_files_dictionary(self, *args):
super(IndexWhiteNoise, self).update_files_dictionary(*args)
index_page_suffix = '/' + self.INDEX_NAME
index_name_length = len(self.INDEX_NAME)
directory_indexes = {}
for url, static_file in self.files.items():
if url.endswith(index_page_suffix):
# For each index file found, add a corresponding URL->content mapping
# for the file's parent directory, so that the index page is served for
# the bare directory URL ending in '/'.
parent_directory_url = url[:-index_name_length]
directory_indexes[parent_directory_url] = static_file
self.files.update(directory_indexes)
def find_file(self, url):
# In debug mode, find_file() is used to serve files directly from the filesystem
# instead of using the list in `self.files`, so we append the index filename so
# that will be served if present.
if url.endswith('/'):
url += self.INDEX_NAME
return super(IndexWhiteNoise, self).find_file(url)
@edmorley thanks, works splendidly even when DEBUG=False
. You made a small typo, though – replace CustomWhiteNoise
with IndexWhiteNoise
.
@evansd would it be possible to add this IndexWhiteNoise
middleware to WhiteNoise itself and maintain it together with the rest of the package? Pretty please django.contrib.staticfiles.views.serve
doesn't work in production when DEBUG=False
. IndexWhiteNoise
would work for that case.
@metakermit, thank you - typo fixed :-)
@metakermit I'll consider it. It's the "and maintain it" bit of your request that worries me ;) WhiteNoise has already got a lot more complicated than I first imagined in would and I'm wary of adding more and more features.
@evansd @edmorley I realised that my use case is a bit wider than just serving index.html on /
. I also needed support for frontend routing, using solutions such as react-router. I ended up extending the middleware a bit and overriding a few more WhiteNoise methods.
Since I understand that making this a part of WhiteNoise doesn't make sense based on what @evansd said about being wary of adding new features, I started a new project I named django-spa. The idea would be to use WhiteNoise as a dependency (keep track of your development) and make sure that a typical use-case of serving single-page apps on /
works (where all non-Django routing is delegated to the JS frontend, including things like 404s). To support this I made a 3-step decision process which I made sure works both on development (DEBUG=True
) and production deployments (DEBUG=False
, serving files from the self.files
dictionary):
- try to find a static file either on
/
or/static/
(or indexl.html if/
is requested) - see if Django matches the url (so that Django admin and templates still work)
- everything else gets routed to
/
(so that fronted routing can be used)
It's still early stage, as I need to make sure everything works on CDNs too. I'm testing it for an app of my own and plan to extend features as I bump into problems. Feedback welcome :)
I've finally added built-in support for serving index files in the upcoming 4.0 release:
http://whitenoise.evans.io/en/latest/django.html#index-files-django
Amazing - thank you! :-)
I haven't had a chance to test it yet, but I see from the docs that it redirects /foo
to /foo/
, which is something we'd actually explicitly prevented Django from doing in our project, by setting APPEND_SLASH to false (this makes them 404 instead, which is desirable when using django-rest-framework to prevent client libraries accidentally using the non-canonical API URLs and causing thousands of redirects an hour).
Presumably the redirect will now happen regardless, since Django won't see the request?
Could WhiteNoise either not touch URLs of form /foo
and let Django choose whether to redirect them as before, or else honour the value of APPEND_SLASH
?
Oh wait sorry I see that the redirect only occurs for index pages that otherwise exist, and not for all URLs without a trailing slash. This is actually the best of both worlds (we want redirection for static pages, just not the REST API endpoints).
Great! (Was just writing something making that point but you've beaten me to it :)
The combination of autorefresh and index files and handling redirects hasn't made for the prettiest bit of code, but I'm fairly confident it all works correctly.