mirror of
https://github.com/jazzband/django-downloadview.git
synced 2026-03-16 22:40:25 +00:00
Refs #16 - Improved documentation for Nginx optimizations (references to demo project). Refs #18 - Moved temporary_media_root utility from demoproject.download.tests to django_downloadview.test.
This commit is contained in:
parent
78c1941b50
commit
4dd718fba3
17 changed files with 291 additions and 321 deletions
2
README
2
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.
|
||||
|
|
|
|||
42
demo/README
42
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
|
||||
**********
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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<slug>[a-zA-Z0-9_-]+)/$', 'download_document',
|
||||
name='download_document'),
|
||||
url(r'^document-nginx/(?P<slug>[a-zA-Z0-9_-]+)/$',
|
||||
'download_document_nginx', name='download_document_nginx'),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
1
demo/demoproject/nginx/__init__.py
Normal file
1
demo/demoproject/nginx/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
"""Nginx optimizations applied to demoproject.download."""
|
||||
0
demo/demoproject/nginx/models.py
Normal file
0
demo/demoproject/nginx/models.py
Normal file
39
demo/demoproject/nginx/tests.py
Normal file
39
demo/demoproject/nginx/tests.py
Normal file
|
|
@ -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')
|
||||
8
demo/demoproject/nginx/urls.py
Normal file
8
demo/demoproject/nginx/urls.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
"""URL mapping."""
|
||||
from django.conf.urls import patterns, include, url
|
||||
|
||||
|
||||
urlpatterns = patterns('demoproject.nginx.views',
|
||||
url(r'^document-nginx/(?P<slug>[a-zA-Z0-9_-]+)/$',
|
||||
'download_document_nginx', name='download_document_nginx'),
|
||||
)
|
||||
9
demo/demoproject/nginx/views.py
Normal file
9
demo/demoproject/nginx/views.py
Normal file
|
|
@ -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')
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
)
|
||||
|
|
|
|||
42
django_downloadview/test.py
Normal file
42
django_downloadview/test.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
-------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
**********
|
||||
|
|
|
|||
|
|
@ -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 </demo>`:
|
||||
|
||||
: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 </demo>`.
|
||||
|
||||
|
||||
***********
|
||||
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<pk>[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/<filename>``.
|
||||
|
||||
.. 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<pk>[0-9]+/download/$', download, name='download'),
|
||||
)
|
||||
|
||||
|
||||
*************
|
||||
Common issues
|
||||
*************
|
||||
|
|
|
|||
Loading…
Reference in a new issue