diff --git a/README b/README index 39eade4..9780d40 100644 --- a/README +++ b/README @@ -10,7 +10,7 @@ Example, in some urls.py: from django.conf.urls import url, url_patterns from django_downloadview import ObjectDownloadView - from demoproject.download.models import Document # A model with a FileField. + from demoproject.download.models import Document # A model with a FileField # ObjectDownloadView inherits from django.views.generic.BaseDetailView. diff --git a/demo/README b/demo/README index 11f42a2..a826a1f 100644 --- a/demo/README +++ b/demo/README @@ -2,8 +2,8 @@ Demo project ############ -The :file:`demo/` folder holds a demo project to illustrate (and test) -django-downloadview usage. +The :file:`demo/` folder holds a demo project to illustrate django-downloadview +usage. *********************** @@ -47,6 +47,44 @@ at http://localhost:8000/ Browse and use :file:`demo/demoproject/` as a sandbox. +********************************* +Base example provided in the demo +********************************* + +In the "demoproject" project, there is an application called "download". + +:file:`demo/demoproject/settings.py`: + +.. literalinclude:: ../demo/demoproject/settings.py + :language: python + :lines: 33-49 + :emphasize-lines: 44 + +This application holds a ``Document`` model. + +:file:`demo/demoproject/download/models.py`: + +.. literalinclude:: ../demo/demoproject/download/models.py + :language: python + +.. note:: + + The ``storage`` is the default one, i.e. it uses ``settings.MEDIA_ROOT``. + Combined to this ``upload_to`` configuration, files for ``Document`` model + live in :file:`var/media/document/` folder, relative to your + django-downloadview clone root. + +There is a download view named "download_document" for this model: + +:file:`demo/demoproject/download/urls.py`: + +.. literalinclude:: ../demo/demoproject/download/urls.py + :language: python + +As is, Django is to serve the files, i.e. load chunks into memory and stream +them. + + ********** References ********** diff --git a/demo/demoproject/download/models.py b/demo/demoproject/download/models.py index 4c89ae5..0ff248a 100644 --- a/demo/demoproject/download/models.py +++ b/demo/demoproject/download/models.py @@ -1,8 +1,7 @@ from django.db import models -from django.utils.translation import ugettext_lazy as _ class Document(models.Model): """A sample model with a FileField.""" - slug = models.SlugField(verbose_name=_('slug')) - file = models.FileField(verbose_name=_('file'), upload_to='document') + slug = models.SlugField(verbose_name='slug') + file = models.FileField(verbose_name='file', upload_to='document') diff --git a/demo/demoproject/download/tests.py b/demo/demoproject/download/tests.py index 6e426f5..ee631b5 100644 --- a/demo/demoproject/download/tests.py +++ b/demo/demoproject/download/tests.py @@ -1,16 +1,12 @@ -"""Test suite for django-downloadview.""" +"""Test suite for demoproject.download.""" from os import listdir from os.path import abspath, dirname, join -import shutil -import tempfile -from django.conf import settings from django.core.files import File from django.core.urlresolvers import reverse_lazy as reverse from django.test import TestCase -from django.test.utils import override_settings -from django_downloadview.nginx import XAccelRedirectResponse +from django_downloadview.test import temporary_media_root from demoproject.download.models import Document @@ -19,49 +15,11 @@ app_dir = dirname(abspath(__file__)) fixtures_dir = join(app_dir, 'fixtures') -class temporary_media_root(override_settings): - """Context manager or decorator to override settings.MEDIA_ROOT. - - >>> from django.conf import settings - >>> global_media_root = settings.MEDIA_ROOT - >>> with temporary_media_root(): - ... global_media_root == settings.MEDIA_ROOT - False - >>> global_media_root == settings.MEDIA_ROOT - True - - >>> @temporary_media_root - ... def use_temporary_media_root(): - ... return settings.MEDIA_ROOT - >>> tmp_media_root = use_temporary_media_root() - >>> global_media_root == tmp_media_root - False - >>> global_media_root == settings.MEDIA_ROOT - True - - """ - def enable(self): - """Create a temporary directory and use it to override - settings.MEDIA_ROOT.""" - tmp_dir = tempfile.mkdtemp() - self.options['MEDIA_ROOT'] = tmp_dir - super(temporary_media_root, self).enable() - - def disable(self): - """Remove directory settings.MEDIA_ROOT then restore original - setting.""" - shutil.rmtree(settings.MEDIA_ROOT) - super(temporary_media_root, self).disable() - - class DownloadTestCase(TestCase): """Base class for download tests.""" def setUp(self): """Common setup.""" super(DownloadTestCase, self).setUp() - self.download_hello_world_url = reverse('download_hello_world') - self.download_document_url = reverse('download_document', - kwargs={'slug': 'hello-world'}) self.files = {} for f in listdir(fixtures_dir): self.files[f] = abspath(join(fixtures_dir, f)) @@ -70,8 +28,11 @@ class DownloadTestCase(TestCase): class DownloadViewTestCase(DownloadTestCase): """Test generic DownloadView.""" def test_download_hello_world(self): - """Download_hello_world view returns hello-world.txt as attachement.""" - response = self.client.get(self.download_hello_world_url) + """'download_hello_world' view returns hello-world.txt as attachement. + + """ + download_url = reverse('download_hello_world') + response = self.client.get(download_url) self.assertEquals(response.status_code, 200) self.assertEquals(response['Content-Type'], 'text/plain; charset=utf-8') @@ -86,12 +47,14 @@ class ObjectDownloadViewTestCase(DownloadTestCase): """Test generic ObjectDownloadView.""" @temporary_media_root() def test_download_hello_world(self): - """Download_hello_world view returns hello-world.txt as attachement.""" + """'download_document' view returns hello-world.txt as attachement.""" + slug = 'hello-world' + download_url = reverse('download_document', kwargs={'slug': slug}) document = Document.objects.create( - slug='hello-world', + slug=slug, file=File(open(self.files['hello-world.txt'])), ) - response = self.client.get(self.download_document_url) + response = self.client.get(download_url) self.assertEquals(response.status_code, 200) self.assertEquals(response['Content-Type'], 'text/plain; charset=utf-8') @@ -100,24 +63,3 @@ class ObjectDownloadViewTestCase(DownloadTestCase): 'attachment; filename=hello-world.txt') self.assertEqual(open(self.files['hello-world.txt']).read(), response.content) - - -class XAccelRedirectDecoratorTestCase(DownloadTestCase): - @temporary_media_root() - def test_response(self): - document = Document.objects.create( - slug='hello-world', - file=File(open(self.files['hello-world.txt'])), - ) - download_url = reverse('download_document_nginx', - kwargs={'slug': 'hello-world'}) - response = self.client.get(download_url) - self.assertEquals(response.status_code, 200) - self.assertTrue(isinstance(response, XAccelRedirectResponse)) - self.assertEquals(response['Content-Type'], - 'text/plain; charset=utf-8') - self.assertFalse('ContentEncoding' in response) - self.assertEquals(response['Content-Disposition'], - 'attachment; filename=hello-world.txt') - self.assertEquals(response['X-Accel-Redirect'], - '/download-optimized/document/hello-world.txt') diff --git a/demo/demoproject/download/urls.py b/demo/demoproject/download/urls.py index 32da47d..53e4296 100644 --- a/demo/demoproject/download/urls.py +++ b/demo/demoproject/download/urls.py @@ -1,4 +1,4 @@ -"""URLconf for tests.""" +"""URL mapping.""" from django.conf.urls import patterns, include, url @@ -7,6 +7,4 @@ urlpatterns = patterns('demoproject.download.views', name='download_hello_world'), url(r'^document/(?P[a-zA-Z0-9_-]+)/$', 'download_document', name='download_document'), - url(r'^document-nginx/(?P[a-zA-Z0-9_-]+)/$', - 'download_document_nginx', name='download_document_nginx'), ) diff --git a/demo/demoproject/download/views.py b/demo/demoproject/download/views.py index 6c46508..6ba9854 100644 --- a/demo/demoproject/download/views.py +++ b/demo/demoproject/download/views.py @@ -1,7 +1,6 @@ from os.path import abspath, dirname, join from django_downloadview import DownloadView, ObjectDownloadView -from django_downloadview.nginx import x_accel_redirect from demoproject.download.models import Document @@ -15,7 +14,3 @@ download_hello_world = DownloadView.as_view(filename=hello_world_file, storage=None) download_document = ObjectDownloadView.as_view(model=Document) - -download_document_nginx = x_accel_redirect(download_document, - media_root='/var/www/files', - media_url='/download-optimized') diff --git a/demo/demoproject/nginx/__init__.py b/demo/demoproject/nginx/__init__.py new file mode 100644 index 0000000..53fd31e --- /dev/null +++ b/demo/demoproject/nginx/__init__.py @@ -0,0 +1 @@ +"""Nginx optimizations applied to demoproject.download.""" diff --git a/demo/demoproject/nginx/models.py b/demo/demoproject/nginx/models.py new file mode 100644 index 0000000..e69de29 diff --git a/demo/demoproject/nginx/tests.py b/demo/demoproject/nginx/tests.py new file mode 100644 index 0000000..9a872ef --- /dev/null +++ b/demo/demoproject/nginx/tests.py @@ -0,0 +1,39 @@ +"""Test suite for demoproject.nginx.""" +from django.core.files import File +from django.core.urlresolvers import reverse_lazy as reverse + +from django_downloadview.nginx import assert_x_accel_redirect +from django_downloadview.test import temporary_media_root + +from demoproject.download.models import Document +from demoproject.download.tests import DownloadTestCase + + +class XAccelRedirectDecoratorTestCase(DownloadTestCase): + @temporary_media_root() + def test_response(self): + """'download_document_nginx' view returns a valid X-Accel response.""" + document = Document.objects.create( + slug='hello-world', + file=File(open(self.files['hello-world.txt'])), + ) + download_url = reverse('download_document_nginx', + kwargs={'slug': 'hello-world'}) + response = self.client.get(download_url) + self.assertEquals(response.status_code, 200) + # Validation shortcut: assert_x_accel_redirect. + assert_x_accel_redirect( + self, + response, + content_type="text/plain; charset=utf-8", + charset="utf-8", + basename="hello-world.txt", + redirect_url="/download-optimized/document/hello-world.txt", + expires=None, + with_buffering=None, + limit_rate=None) + # Check some more items, because this test is part of + # django-downloadview tests. + self.assertFalse('ContentEncoding' in response) + self.assertEquals(response['Content-Disposition'], + 'attachment; filename=hello-world.txt') diff --git a/demo/demoproject/nginx/urls.py b/demo/demoproject/nginx/urls.py new file mode 100644 index 0000000..b36d6c2 --- /dev/null +++ b/demo/demoproject/nginx/urls.py @@ -0,0 +1,8 @@ +"""URL mapping.""" +from django.conf.urls import patterns, include, url + + +urlpatterns = patterns('demoproject.nginx.views', + url(r'^document-nginx/(?P[a-zA-Z0-9_-]+)/$', + 'download_document_nginx', name='download_document_nginx'), +) diff --git a/demo/demoproject/nginx/views.py b/demo/demoproject/nginx/views.py new file mode 100644 index 0000000..68a104b --- /dev/null +++ b/demo/demoproject/nginx/views.py @@ -0,0 +1,9 @@ +"""Views.""" +from django_downloadview.nginx import x_accel_redirect + +from demoproject.download.views import download_document + + +download_document_nginx = x_accel_redirect(download_document, + media_root='/var/www/files', + media_url='/download-optimized') diff --git a/demo/demoproject/settings.py b/demo/demoproject/settings.py index 9328293..57ccda1 100755 --- a/demo/demoproject/settings.py +++ b/demo/demoproject/settings.py @@ -1,164 +1,73 @@ -# Django settings for Django-DownloadView demo project. +"""Django settings for Django-DownloadView demo project.""" from os.path import abspath, dirname, join + +# Configure some relative directories. demoproject_dir = dirname(abspath(__file__)) demo_dir = dirname(demoproject_dir) root_dir = dirname(demo_dir) data_dir = join(root_dir, 'var') -DEBUG = True -TEMPLATE_DEBUG = DEBUG -ADMINS = ( - # ('Your Name', 'your_email@example.com'), -) +# Mandatory settings. +ROOT_URLCONF = 'demoproject.urls' +WSGI_APPLICATION = 'demoproject.wsgi.application' -MANAGERS = ADMINS +# Database. DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': join(data_dir, 'db.sqlite'), - 'USER': '', - 'PASSWORD': '', - 'HOST': '', - 'PORT': '', } } -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# In a Windows environment this must be set to your system time zone. -TIME_ZONE = 'America/Chicago' -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True - -# If you set this to False, Django will not format dates, numbers and -# calendars according to the current locale. -USE_L10N = True - -# If you set this to False, Django will not use timezone-aware datetimes. -USE_TZ = True - -# Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/home/media/media.lawrence.com/media/" +# Media and static files. MEDIA_ROOT = join(data_dir, 'media') - -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -# trailing slash. -# Examples: "http://media.lawrence.com/media/", "http://example.com/media/" -MEDIA_URL = '' - -# Absolute path to the directory static files should be collected to. -# Don't put anything in this directory yourself; store your static files -# in apps' "static/" subdirectories and in STATICFILES_DIRS. -# Example: "/home/media/media.lawrence.com/static/" +MEDIA_URL = '/media/' STATIC_ROOT = join(data_dir, 'static') - -# URL prefix for static files. -# Example: "http://media.lawrence.com/static/" STATIC_URL = '/static/' -# Additional locations of static files -STATICFILES_DIRS = ( - # Put strings here, like "/home/html/static" or "C:/www/django/static". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. -) - -# List of finder classes that know how to find static files in -# various locations. -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -# 'django.contrib.staticfiles.finders.DefaultStorageFinder', -) - -# Make this unique, and don't share it with anybody. -SECRET_KEY = '123456789' - -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -# 'django.template.loaders.eggs.Loader', -) - -MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - # Uncomment the next line for simple clickjacking protection: - # 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) - -ROOT_URLCONF = 'demoproject.urls' - -# Python dotted path to the WSGI application used by Django's runserver. -WSGI_APPLICATION = 'demoproject.wsgi.application' - -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. -) +# Applications. INSTALLED_APPS = ( + # Standard Django applications. 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', - # Uncomment the next line to enable the admin: - # 'django.contrib.admin', - # Uncomment the next line to enable admin documentation: - # 'django.contrib.admindocs', + # The actual django-downloadview demo. 'demoproject', - 'demoproject.download', + 'demoproject.download', # Sample standard download views. + 'demoproject.nginx', # Sample optimizations for Nginx. + # For test purposes. The demo project is part of django-downloadview + # test suite. 'django_nose', ) -# A sample logging configuration. The only tangible logging -# performed by this configuration is to send an email to -# the site admins on every HTTP 500 error when DEBUG=False. -# See http://docs.djangoproject.com/en/dev/topics/logging for -# more details on how to customize your logging configuration. -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse' - } - }, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' - } - }, - 'loggers': { - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': True, - }, - } -} +# Default middlewares. You may alter the list later. +MIDDLEWARE_CLASSES = [ + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', +] + + +# Uncomment the following lines to enable global Nginx optimizations. +#MIDDLEWARE_CLASSES.append('django_downloadview.nginx.XAccelRedirectMiddleware') +#NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT = MEDIA_ROOT +#NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL = "/proxied-download" + + +# Development configuratio. +DEBUG = True +TEMPLATE_DEBUG = DEBUG TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' NOSE_ARGS = ['--verbose', '--nocapture', diff --git a/demo/demoproject/urls.py b/demo/demoproject/urls.py index 2057628..d7597a1 100755 --- a/demo/demoproject/urls.py +++ b/demo/demoproject/urls.py @@ -6,6 +6,10 @@ home = TemplateView.as_view(template_name='home.html') urlpatterns = patterns('', + # Standard download views. url(r'^download/', include('demoproject.download.urls')), + # Nginx optimizations. + url(r'^nginx/', include('demoproject.nginx.urls')), + # An informative page. url(r'', home, name='home') ) diff --git a/django_downloadview/test.py b/django_downloadview/test.py new file mode 100644 index 0000000..fa9b2f0 --- /dev/null +++ b/django_downloadview/test.py @@ -0,0 +1,42 @@ +"""Testing utilities.""" +import shutil +import tempfile + +from django.conf import settings +from django.test.utils import override_settings + + +class temporary_media_root(override_settings): + """Context manager or decorator to override settings.MEDIA_ROOT. + + >>> from django_downloadview.test import temporary_media_root + >>> from django.conf import settings + >>> global_media_root = settings.MEDIA_ROOT + >>> with temporary_media_root(): + ... global_media_root == settings.MEDIA_ROOT + False + >>> global_media_root == settings.MEDIA_ROOT + True + + >>> @temporary_media_root() + ... def use_temporary_media_root(): + ... return settings.MEDIA_ROOT + >>> tmp_media_root = use_temporary_media_root() + >>> global_media_root == tmp_media_root + False + >>> global_media_root == settings.MEDIA_ROOT + True + + """ + def enable(self): + """Create a temporary directory and use it to override + settings.MEDIA_ROOT.""" + tmp_dir = tempfile.mkdtemp() + self.options['MEDIA_ROOT'] = tmp_dir + super(temporary_media_root, self).enable() + + def disable(self): + """Remove directory settings.MEDIA_ROOT then restore original + setting.""" + shutil.rmtree(settings.MEDIA_ROOT) + super(temporary_media_root, self).disable() diff --git a/docs/api/django_downloadview.txt b/docs/api/django_downloadview.txt index c790b25..b919a52 100644 --- a/docs/api/django_downloadview.txt +++ b/docs/api/django_downloadview.txt @@ -41,6 +41,14 @@ django_downloadview Package :undoc-members: :show-inheritance: +:mod:`test` Module +------------------ + +.. automodule:: django_downloadview.test + :members: + :undoc-members: + :show-inheritance: + :mod:`utils` Module ------------------- diff --git a/docs/dev.txt b/docs/dev.txt index 96d21bf..9657a73 100644 --- a/docs/dev.txt +++ b/docs/dev.txt @@ -95,6 +95,14 @@ Test and build Use `the Makefile`_. +********************* +Demo project included +********************* + +The :doc:`/demo` is part of the tests. Maintain it along with code and +documentation. + + ********** References ********** diff --git a/docs/optimizations/nginx.txt b/docs/optimizations/nginx.txt index 4971b52..a9eb83d 100644 --- a/docs/optimizations/nginx.txt +++ b/docs/optimizations/nginx.txt @@ -15,70 +15,104 @@ See `Nginx X-accel documentation`_ for details. Configure some download view **************************** -As an example, let's consider an application called "myapp". +Let's start in the situation described in the :doc:`demo application `: -:file:`settings.py`: +* a project "demoproject" +* an application "demoproject.download" +* a :py:class:`django_downloadview.views.ObjectDownloadView` view serves files + of a "Document" model. + +We are to make it more efficient with Nginx. + +.. note:: + + Examples below are taken from the :doc:`demo project `. + + +*********** +Write tests +*********** + +Use :py:func:`django_downloadview.nginx.assert_x_accel_redirect` function as +a shortcut in your tests. + +:file:`demo/demoproject/nginx/tests.py`: + +.. literalinclude:: ../../demo/demoproject/nginx/tests.py + :language: python + :emphasize-lines: 5, 25-34 + +Right now, this test should fail, since you haven't implemented the view yet. + + +************ +Setup Django +************ + +At the end of this setup, the test should pass, but you still have to `setup +Nginx`_! + +You have two options: global setup with a middleware, or per-view setup with +decorators. + +Global delegation, with XAccelRedirectMiddleware +================================================ + +If you want to delegate all file downloads to Nginx, then use +:py:class:`django_downloadview.nginx.XAccelRedirectMiddleware`. + +Register it in your settings: .. code-block:: python - INSTALLED_APPS = ( + MIDDLEWARE_CLASSES = ( # ... - 'myapp', + 'django_downloadview.nginx.XAccelRedirectMiddleware', # ... ) - MYAPP_STORAGE_LOCATION = '/var/www/files/' # Could be MEDIA_ROOT for public - # files. -This application holds a ``Document`` model. - -:file:`myapp/models.py`: +Setup the middleware: .. code-block:: python - from django.conf import settings - from django.core.files.storage import FileSystemStorage - from django.db import models + NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT = MEDIA_ROOT # Could be elsewhere. + NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL = '/proxied-download' - - storage = FileSystemStorage(location=settings.MYAPP_STORAGE_LOCATION) - - - class Document(models.Model): - file = models.FileField(storage=storage, upload_to='document') - -Notice the ``storage`` and ``upload_to`` parameters: files for ``Document`` -model live in :file:`/var/www/files/document/` folder. - -Then we configured a download view for this model, restricted to authenticated -users: - -:file:`myapp/urls.py`: +Optionally fine-tune the middleware. Default values are ``None``, which means +"use Nginx's defaults". .. code-block:: python - from django.conf.urls import url, url_patterns - from django.contrib.auth.decorators import login_required + NGINX_DOWNLOAD_MIDDLEWARE_EXPIRES = False # Force no expiration. + NGINX_DOWNLOAD_MIDDLEWARE_WITH_BUFFERING = False # Force buffering off. + NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE = False # Force limit rate off. - from django_downloadview import ObjectDownloadView +Local delegation, with x_accel_redirect decorator +================================================= - from myapp.models import Document +If you want to delegate file downloads to Nginx on a per-view basis, then use +:py:func:`django_downloadview.nginx.x_accel_redirect` decorator. + +:file:`demo/demoproject/nginx/views.py`: + +.. literalinclude:: ../../demo/demoproject/nginx/views.py + :language: python + +And use it in som URL conf, as an example in +:file:`demo/demoproject/nginx/urls.py`: + +.. literalinclude:: ../../demo/demoproject/nginx/urls.py + :language: python + +.. note:: + + In real life, you'd certainly want to replace the "download_document" view + instead of registering a new view. - download = login_required(ObjectDownloadView.as_view(model=Document)) - - url_patterns = ('', - url('^document/(?P[0-9]+/download/$', download, name='download'), - ) - -As is, Django is to serve the files, i.e. load chunks into memory and stream -them. - -Nginx is much more efficient for the actual streaming... Let's use it! - - -*************** -Configure Nginx -*************** +*********** +Setup Nginx +*********** See `Nginx X-accel documentation`_ for details. @@ -103,7 +137,7 @@ Here is what you could have in :file:`/etc/nginx/sites-available/default`: # # See http://wiki.nginx.org/X-accel # and https://github.com/benoitbryon/django-downloadview - location /optimized-download { + location /proxied-download { internal; # Location to files on disk. # See Django's settings.NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT @@ -124,7 +158,7 @@ section. .. note:: - ``/optimized-download`` is not available for the client, i.e. users + ``/proxied-download`` is not available for the client, i.e. users won't be able to download files via ``/optimized-download/``. .. warning:: @@ -132,70 +166,6 @@ section. Make sure Nginx can read the files to download! Check permissions. -************************************************ -Global delegation, with XAccelRedirectMiddleware -************************************************ - -If you want to delegate all file downloads to Nginx, then use -:py:class:`django_downloadview.nginx.XAccelRedirectMiddleware`. - -Register it in your settings: - -.. code-block:: python - - MIDDLEWARE_CLASSES = ( - # ... - 'django_downloadview.nginx.XAccelRedirectMiddleware', - # ... - ) - -Setup the middleware: - -.. code-block:: python - - NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT = MYAPP_STORAGE_LOCATION - NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL = '/optimized-download' - -Optionally fine-tune the middleware. Default values are ``None``, which means -"use Nginx's defaults". - -.. code-block:: python - - NGINX_DOWNLOAD_MIDDLEWARE_EXPIRES = False # Force no expiration. - NGINX_DOWNLOAD_MIDDLEWARE_WITH_BUFFERING = False # Force buffering off. - NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE = False # Force limit rate off. - - -************************************************* -Local delegation, with x_accel_redirect decorator -************************************************* - -If you want to delegate file downloads to Nginx on a per-view basis, then use -:py:func:`django_downloadview.nginx.x_accel_redirect` decorator. - -Adapt :file:`myapp/urls.py`: - -.. code-block:: diff - - from django.conf.urls import url, url_patterns - from django.contrib.auth.decorators import login_required - - from django_downloadview import ObjectDownloadView - + from django_downloadview.nginx import x_accel_redirect - - from myapp.models import Document - - - download = login_required(ObjectDownloadView.as_view(model=Document)) - + download = x_accel_redirect(download, - + media_root=settings.MY_APP_STORAGE_LOCATION, - + media_url='/optimized-download') - - url_patterns = ('', - url('^document/(?P[0-9]+/download/$', download, name='download'), - ) - - ************* Common issues *************