├── LICENSE ├── README.rst ├── dj_static.py └── setup.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Kenneth Reitz 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | DJ-Static 2 | ========= 3 | 4 | This is a simple Django middleware utility that allows you to properly 5 | serve static assets from production with a WSGI server like Gunicorn. 6 | 7 | .. note:: You should probably use `WhiteNoise `_ instead. It's better software. 8 | 9 | Django `doesn't recommend `_ 10 | the production use of its static file server for a number of reasons. 11 | There exists, however, a lovely WSGI application aptly named `Static `_. 12 | 13 | .. image:: http://farm8.staticflickr.com/7387/8907351990_58677d7c35_z.jpg 14 | 15 | "Finally, a super-simple way of serving assets in Django that’ll actually perform well" — `@jacobian `_ 16 | 17 | It is suitable for the production use of static file serving, unlike Django. 18 | Enjoy! 19 | 20 | Shouldn't I use a CDN? 21 | ---------------------- 22 | 23 | If you have to ask that question, there's actually quite a good chance you don't. 24 | Static responses aren't very different than dynamic ones. 25 | 26 | If you're running a top-tier application, optimizing for delivery and reducing 27 | frontend load, you will want to explore using a CDN with 28 | `Django-Storages `_. 29 | 30 | 31 | Usage 32 | ----- 33 | 34 | :: 35 | 36 | $ pip install dj-static 37 | 38 | Configure your static assets in ``settings.py``:: 39 | 40 | STATIC_ROOT = 'staticfiles' 41 | STATIC_URL = '/static/' 42 | 43 | Then, update your ``wsgi.py`` file to use dj-static:: 44 | 45 | from django.core.wsgi import get_wsgi_application 46 | from dj_static import Cling 47 | 48 | application = Cling(get_wsgi_application()) 49 | 50 | File uploads (optional) 51 | ^^^^^^^^^^^^^^^^^^^^^^^ 52 | 53 | In case you also want to serve media files that were uploaded to ``MEDIA_ROOT``:: 54 | 55 | MEDIA_ROOT = 'media' 56 | MEDIA_URL = '/media/' 57 | 58 | Then again, update your ``wsgi.py`` file:: 59 | 60 | from django.core.wsgi import get_wsgi_application 61 | from dj_static import Cling, MediaCling 62 | 63 | application = Cling(MediaCling(get_wsgi_application())) 64 | -------------------------------------------------------------------------------- /dj_static.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import static 4 | 5 | from django.conf import settings 6 | from django.core.handlers.wsgi import WSGIHandler 7 | from django.contrib.staticfiles.handlers import StaticFilesHandler as DebugHandler 8 | 9 | try: 10 | from urllib.parse import urlparse 11 | except ImportError: # Python 2 12 | from urlparse import urlparse 13 | from django.contrib.staticfiles import utils 14 | 15 | try: 16 | from django.core.handlers.wsgi import get_path_info 17 | except ImportError: # django < 1.7 18 | try: 19 | from django.core.handlers.base import get_path_info 20 | except ImportError: # django < 1.5 21 | import sys 22 | py3 = sys.version_info[0] == 3 23 | 24 | def get_path_info(environ): 25 | """ 26 | Returns the HTTP request's PATH_INFO as a unicode string. 27 | """ 28 | path_info = environ.get('PATH_INFO', str('/')) 29 | # Under Python 3, strings in environ are decoded with ISO-8859-1; 30 | # re-encode to recover the original bytestring provided by the web server. 31 | if py3: 32 | path_info = path_info.encode('iso-8859-1') 33 | # It'd be better to implement URI-to-IRI decoding, see #19508. 34 | return path_info.decode('utf-8') 35 | 36 | 37 | class Cling(WSGIHandler): 38 | """WSGI middleware that intercepts calls to the static files 39 | directory, as defined by the STATIC_URL setting, and serves those files. 40 | """ 41 | def __init__(self, application, base_dir=None, base_url=None, ignore_debug=False): 42 | self.application = application 43 | self.ignore_debug = ignore_debug 44 | if not base_dir: 45 | base_dir = self.get_base_dir() 46 | self.base_url = urlparse(base_url or self.get_base_url()) 47 | 48 | self.cling = static.Cling(base_dir) 49 | try: 50 | self.debug_cling = DebugHandler(application, base_dir=base_dir) 51 | except TypeError: 52 | self.debug_cling = DebugHandler(application) 53 | 54 | super(Cling, self).__init__() 55 | 56 | def get_base_dir(self): 57 | return settings.STATIC_ROOT 58 | 59 | def get_base_url(self): 60 | utils.check_settings() 61 | return settings.STATIC_URL 62 | 63 | @property 64 | def debug(self): 65 | return settings.DEBUG 66 | 67 | def _transpose_environ(self, environ): 68 | """Translates a given environ to static.Cling's expectations.""" 69 | environ['PATH_INFO'] = environ['PATH_INFO'][len(self.base_url[2]) - 1:] 70 | return environ 71 | 72 | def _should_handle(self, path): 73 | """Checks if the path should be handled. Ignores the path if: 74 | 75 | * the host is provided as part of the base_url 76 | * the request's path isn't under the media path (or equal) 77 | """ 78 | return path.startswith(self.base_url[2]) and not self.base_url[1] 79 | 80 | def __call__(self, environ, start_response): 81 | # Hand non-static requests to Django 82 | try: 83 | if not self._should_handle(get_path_info(environ)): 84 | return self.application(environ, start_response) 85 | except UnicodeDecodeError: 86 | # Apparently a malformed URL. Just hand it to Django 87 | # for it to respond as it sees fit. 88 | return self.application(environ, start_response) 89 | 90 | # Serve static requests from static.Cling 91 | if not self.debug or self.ignore_debug: 92 | environ = self._transpose_environ(environ) 93 | return self.cling(environ, start_response) 94 | # Serve static requests in debug mode from StaticFilesHandler 95 | else: 96 | return self.debug_cling(environ, start_response) 97 | 98 | 99 | class MediaCling(Cling): 100 | 101 | def __init__(self, application, base_dir=None): 102 | super(MediaCling, self).__init__(application, base_dir=base_dir) 103 | # override callable attribute with method 104 | self.debug_cling = self._debug_cling 105 | 106 | def _debug_cling(self, environ, start_response): 107 | environ = self._transpose_environ(environ) 108 | return self.cling(environ, start_response) 109 | 110 | def get_base_dir(self): 111 | return settings.MEDIA_ROOT 112 | 113 | def get_base_url(self): 114 | return settings.MEDIA_URL 115 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | dj-static 4 | ~~~~~~~~~ 5 | 6 | This is a simple Django middleware utility that allows you to properly 7 | serve static assets from production with a WSGI server like Gunicorn. 8 | 9 | Django `doesn't recommend `_ 10 | the production use of its static file server for a number of reasons. 11 | There exists, however, a lovely WSGI application aptly named `Static `_. 12 | 13 | It is suitable for the production use of static file serving, unlike Django. 14 | 15 | Usage 16 | ----- 17 | 18 | Configure your static assets in ``settings.py``:: 19 | 20 | STATIC_ROOT = 'staticfiles' 21 | STATIC_URL = '/static/' 22 | 23 | Then, update your ``wsgi.py`` file to use dj-static:: 24 | 25 | from django.core.wsgi import get_wsgi_application 26 | from dj_static import Cling 27 | 28 | application = Cling(get_wsgi_application()) 29 | 30 | """ 31 | 32 | from setuptools import setup 33 | 34 | setup( 35 | name='dj-static', 36 | version='0.0.6', 37 | url='https://github.com/kennethreitz/dj-static', 38 | license='BSD', 39 | author='Kenneth Reitz', 40 | author_email='me@kennethreitz.com', 41 | description='Serve production static files with Django.', 42 | long_description=__doc__, 43 | py_modules=['dj_static'], 44 | zip_safe=False, 45 | install_requires=['static'], 46 | include_package_data=True, 47 | platforms='any', 48 | classifiers=[ 49 | 'Environment :: Web Environment', 50 | 'Intended Audience :: Developers', 51 | 'License :: OSI Approved :: BSD License', 52 | 'Operating System :: OS Independent', 53 | 'Programming Language :: Python', 54 | 'Programming Language :: Python :: 3', 55 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 56 | 'Topic :: Software Development :: Libraries :: Python Modules' 57 | ] 58 | ) 59 | --------------------------------------------------------------------------------