Initial
This commit is contained in:
commit
fa22e7a03f
9 changed files with 390 additions and 0 deletions
215
rednoise/base.py
Normal file
215
rednoise/base.py
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from os.path import isfile
|
||||
|
||||
try:
|
||||
import urlparse
|
||||
except ImportError:
|
||||
import urllib.parse as urlparse
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.contrib.staticfiles import finders
|
||||
|
||||
from whitenoise.django import DjangoWhiteNoise
|
||||
|
||||
A404 = '404 NOT FOUND'
|
||||
PI = 'PATH_INFO'
|
||||
|
||||
|
||||
class DjangoRedNoise(DjangoWhiteNoise):
|
||||
rednoise_config_attrs = [
|
||||
'should_serve_static',
|
||||
'should_serve_media'
|
||||
]
|
||||
|
||||
debug = False
|
||||
should_serve_static = True
|
||||
should_serve_media = True
|
||||
|
||||
def __init__(self, application):
|
||||
"""Basic init stuff. We allow overriding a few extra things.
|
||||
"""
|
||||
self.charset = settings.FILE_CHARSET
|
||||
self.application = application
|
||||
self.staticfiles_dirs = []
|
||||
self.static_files = {}
|
||||
self.media_files = {}
|
||||
|
||||
# Allow settings to override default attributes
|
||||
# We check for existing WHITENOISE_{} stuff to be compatible, but then
|
||||
# add a few RedNoise specific ones in order to not be too confusing.
|
||||
self.check_and_set_settings('WHITENOISE_{}', self.config_attrs)
|
||||
self.check_and_set_settings('REDNOISE_{}', self.rednoise_config_attrs)
|
||||
|
||||
# If DEBUG=True in settings, then we'll just default Rednoise to debug.
|
||||
try:
|
||||
setattr(self, 'debug', getattr(settings, 'DEBUG'))
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
# Grab the various roots we care about.
|
||||
if self.should_serve_static:
|
||||
self.static_root, self.static_prefix = self.get_structure('STATIC')
|
||||
try:
|
||||
setattr(self, 'staticfiles_dirs', getattr(
|
||||
settings, 'STATICFILES_DIRS'
|
||||
))
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if self.should_serve_media:
|
||||
self.media_root, self.media_prefix = self.get_structure('MEDIA')
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""Checks to see if a request is inside our designated media or static
|
||||
configurations.
|
||||
"""
|
||||
if self.should_serve_static and self.is_static(environ[PI]):
|
||||
asset = self.load_static_file(environ[PI])
|
||||
if asset is not None:
|
||||
return self.serve(asset, environ, start_response)
|
||||
else:
|
||||
start_response(A404, [('Content-Type', 'text/plain')])
|
||||
return ['Not Found']
|
||||
|
||||
if self.should_serve_media and self.is_media(environ[PI]):
|
||||
asset = self.load_media_file(environ[PI])
|
||||
if asset is not None:
|
||||
return self.serve(asset, environ, start_response)
|
||||
else:
|
||||
start_response(A404, [('Content-Type', 'text/plain')])
|
||||
return ['Not Found']
|
||||
|
||||
return self.application(environ, start_response)
|
||||
|
||||
def file_not_modified(self, static_file, environ):
|
||||
"""We just hook in here to always return false (i.e, it was modified)
|
||||
in DEBUG scenarios. This is optimal for development/reloading
|
||||
scenarios.
|
||||
|
||||
In a production scenario, you want the original Whitenoise setup, so
|
||||
super().
|
||||
"""
|
||||
if self.debug:
|
||||
return False
|
||||
return super(DjangoRedNoise, self).file_not_modified(
|
||||
static_file,
|
||||
environ
|
||||
)
|
||||
|
||||
def add_cache_headers(self, static_file, url):
|
||||
"""Again, we hook in here to blank on adding cache headers in DEBUG
|
||||
scenarios. This is optimal for development/reloading
|
||||
scenarios.
|
||||
|
||||
In a production scenario, you want the original Whitenoise setup, so
|
||||
super().
|
||||
"""
|
||||
if self.debug:
|
||||
return
|
||||
super(DjangoRedNoise, self).add_cache_headers(static_file, url)
|
||||
|
||||
def check_and_set_settings(self, settings_key, attributes):
|
||||
"""Checks settings to see if we should override something.
|
||||
"""
|
||||
for attr in attributes:
|
||||
key = settings_key.format(attr.upper())
|
||||
try:
|
||||
setattr(self, attr, getattr(settings, key))
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def get_structure(self, key):
|
||||
"""This code is almost verbatim from the Whitenoise project Django
|
||||
integration. Little reason to change it, short of string substitution.
|
||||
"""
|
||||
url = getattr(settings, '%s_URL' % key, None)
|
||||
root = getattr(settings, '%s_ROOT' % key, None)
|
||||
if not url or not root:
|
||||
raise ImproperlyConfigured('%s_URL and %s_ROOT \
|
||||
must be configured to use RedNoise' % (key, key))
|
||||
prefix = urlparse.urlparse(url).path
|
||||
prefix = '/{}/'.format(prefix.strip('/'))
|
||||
return root, prefix
|
||||
|
||||
def is_static(self, path):
|
||||
"""Checks to see if a given path is trying to be all up in
|
||||
our static director(y||ies).
|
||||
"""
|
||||
return path[:len(self.static_prefix)] == self.static_prefix
|
||||
|
||||
def add_static_file(self, path):
|
||||
"""Custom, ish. Adopts the same approach as Whitenoise, but instead
|
||||
handles creating of a File object per each valid static/media request.
|
||||
This is then cached for lookup later if need-be.
|
||||
|
||||
If REDNOISE_DEBUG is True, then this will also scan extra
|
||||
STATICFILES_DIRS.
|
||||
|
||||
See also: self.add_media_file()
|
||||
"""
|
||||
file_path = None
|
||||
is_file = False
|
||||
if self.debug:
|
||||
result = finders.find(path.replace(self.static_prefix, ''))
|
||||
if result:
|
||||
is_file = True
|
||||
file_path = result
|
||||
|
||||
if not is_file: # Account for stuff like admin, etc
|
||||
file_path = ('%s/%s' % (
|
||||
self.static_root, path.replace(self.static_prefix, '')
|
||||
)).replace('\\', '/')
|
||||
is_file = isfile(file_path)
|
||||
|
||||
if is_file:
|
||||
files = {}
|
||||
files[path] = self.get_static_file(file_path, path)
|
||||
if not self.debug:
|
||||
self.find_gzipped_alternatives(files)
|
||||
self.static_files.update(files)
|
||||
|
||||
def load_static_file(self, path):
|
||||
"""Retrieves a static file, optimizing along the way.
|
||||
Very possible it can return None. TODO: perhaps optimize that
|
||||
use case somehow.
|
||||
"""
|
||||
asset = self.static_files.get(path)
|
||||
if asset is None or self.debug:
|
||||
self.add_static_file(path)
|
||||
asset = self.static_files.get(path)
|
||||
|
||||
return asset
|
||||
|
||||
def is_media(self, path):
|
||||
"""Checks to see if a given path is trying to be all up in our
|
||||
media director(y||ies).
|
||||
"""
|
||||
return path[:len(self.media_prefix)] == self.media_prefix
|
||||
|
||||
def add_media_file(self, path):
|
||||
"""Custom, ish. Adopts the same approach as Whitenoise, but instead
|
||||
handles creating of a File object per each valid static/media request.
|
||||
This is then cached for lookup later if need-be.
|
||||
|
||||
Media and static assets have differing properties by their very
|
||||
nature, so we have separate methods.
|
||||
"""
|
||||
file_path = ('%s/%s' % (
|
||||
self.media_root, path.replace(self.media_prefix, '')
|
||||
)).replace('\\', '/')
|
||||
if isfile(file_path):
|
||||
files = {}
|
||||
files[path] = self.get_static_file(file_path, path)
|
||||
self.media_files.update(files)
|
||||
|
||||
def load_media_file(self, path):
|
||||
"""Retrieves a media file, optimizing along the way.
|
||||
"""
|
||||
asset = self.media_files.get(path)
|
||||
if asset is None or self.debug:
|
||||
self.add_media_file(path)
|
||||
asset = self.media_files.get(path)
|
||||
|
||||
return asset
|
||||
Reference in a new issue