Merge pull request #150 from benoitbryon/add-black

Add black support.
This commit is contained in:
Rémy HUBSCHER 2020-01-07 16:00:46 +01:00 committed by GitHub
commit bdf0ba2188
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 1050 additions and 890 deletions

16
.isort.cfg Normal file
View file

@ -0,0 +1,16 @@
[settings]
# # Needed for black compatibility
multi_line_output=3
include_trailing_comma=True
force_grid_wrap=0
line_length=88
combine_as_imports=True
# List sections with django and
known_django=django
known_downloadview=django_downloadview
sections=FUTURE,STDLIB,DJANGO,DOWNLOADVIEW,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
# If set, imports will be sorted within their section independent to the import_type.
force_sort_within_sections=True

View file

@ -7,7 +7,8 @@
#
PIP = pip
TOX = tox
BLACK = black
ISORT = isort
#: help - Display callable targets.
.PHONY: help
@ -96,3 +97,11 @@ runserver: demo
.PHONY: release
release:
$(TOX) -e release
.PHONY: black
black:
$(BLACK) demo tests django_downloadview
.PHONY: isort
isort:
$(ISORT) --recursive django_downloadview tests demo

View file

@ -5,39 +5,41 @@ import django.test
from django_downloadview.apache import assert_x_sendfile
from demoproject.compat import reverse
from demoproject.apache.views import storage, storage_dir
from demoproject.compat import reverse
def setup_file():
if not os.path.exists(storage_dir):
os.makedirs(storage_dir)
storage.save('hello-world.txt', ContentFile(u'Hello world!\n'))
storage.save("hello-world.txt", ContentFile("Hello world!\n"))
class OptimizedByMiddlewareTestCase(django.test.TestCase):
def test_response(self):
"""'apache:optimized_by_middleware' returns X-Sendfile response."""
setup_file()
url = reverse('apache:optimized_by_middleware')
url = reverse("apache:optimized_by_middleware")
response = self.client.get(url)
assert_x_sendfile(
self,
response,
content_type="text/plain; charset=utf-8",
basename="hello-world.txt",
file_path="/apache-optimized-by-middleware/hello-world.txt")
file_path="/apache-optimized-by-middleware/hello-world.txt",
)
class OptimizedByDecoratorTestCase(django.test.TestCase):
def test_response(self):
"""'apache:optimized_by_decorator' returns X-Sendfile response."""
setup_file()
url = reverse('apache:optimized_by_decorator')
url = reverse("apache:optimized_by_decorator")
response = self.client.get(url)
assert_x_sendfile(
self,
response,
content_type="text/plain; charset=utf-8",
basename="hello-world.txt",
file_path="/apache-optimized-by-decorator/hello-world.txt")
file_path="/apache-optimized-by-decorator/hello-world.txt",
)

View file

@ -1,16 +1,19 @@
"""URL mapping."""
from django.conf.urls import url
from demoproject.compat import patterns
from demoproject.apache import views
from demoproject.compat import patterns
urlpatterns = patterns(
'demoproject.apache.views',
url(r'^optimized-by-middleware/$',
"demoproject.apache.views",
url(
r"^optimized-by-middleware/$",
views.optimized_by_middleware,
name='optimized_by_middleware'),
url(r'^optimized-by-decorator/$',
name="optimized_by_middleware",
),
url(
r"^optimized-by-decorator/$",
views.optimized_by_decorator,
name='optimized_by_decorator'),
name="optimized_by_decorator",
),
)

View file

@ -6,17 +6,19 @@ from django.core.files.storage import FileSystemStorage
from django_downloadview import StorageDownloadView
from django_downloadview.apache import x_sendfile
storage_dir = os.path.join(settings.MEDIA_ROOT, 'apache')
storage = FileSystemStorage(location=storage_dir,
base_url=''.join([settings.MEDIA_URL, 'apache/']))
storage_dir = os.path.join(settings.MEDIA_ROOT, "apache")
storage = FileSystemStorage(
location=storage_dir, base_url="".join([settings.MEDIA_URL, "apache/"])
)
optimized_by_middleware = StorageDownloadView.as_view(storage=storage,
path='hello-world.txt')
optimized_by_middleware = StorageDownloadView.as_view(
storage=storage, path="hello-world.txt"
)
optimized_by_decorator = x_sendfile(
StorageDownloadView.as_view(storage=storage, path='hello-world.txt'),
StorageDownloadView.as_view(storage=storage, path="hello-world.txt"),
source_url=storage.base_url,
destination_dir='/apache-optimized-by-decorator/')
destination_dir="/apache-optimized-by-decorator/",
)

View file

@ -1,22 +1,26 @@
from distutils.version import StrictVersion
from django.utils.version import get_version
from django.utils.version import get_version
try:
from django.conf.urls import patterns # noqa
except ImportError:
def patterns(prefix, *args):
return list(args)
try:
from django.urls import reverse # noqa
except ImportError:
from django.core.urlresolvers import reverse # noqa
if StrictVersion(get_version()) >= StrictVersion('2.0'):
if StrictVersion(get_version()) >= StrictVersion("2.0"):
from django.conf.urls import include as urlinclude # noqa
def include(arg, namespace=None, app_name=None):
return urlinclude((arg, app_name), namespace=namespace)
else:
from django.conf.urls import include # noqa

View file

@ -8,20 +8,20 @@ from demoproject.compat import reverse
class SimpleURLTestCase(django.test.TestCase):
def test_download_response(self):
"""'simple_url' serves 'hello-world.txt' from Github."""
url = reverse('http:simple_url')
url = reverse("http:simple_url")
response = self.client.get(url)
assert_download_response(self,
response,
content='Hello world!\n',
basename='hello-world.txt',
mime_type='text/plain')
assert_download_response(
self,
response,
content="Hello world!\n",
basename="hello-world.txt",
mime_type="text/plain",
)
class AvatarTestCase(django.test.TestCase):
def test_download_response(self):
"""HTTPDownloadView proxies Content-Type header."""
url = reverse('http:avatar_url')
url = reverse("http:avatar_url")
response = self.client.get(url)
assert_download_response(self,
response,
mime_type='image/png')
assert_download_response(self, response, mime_type="image/png")

View file

@ -3,13 +3,8 @@ from django.conf.urls import url
from demoproject.compat import patterns
from demoproject.http import views
urlpatterns = patterns(
'',
url(r'^simple_url/$',
views.simple_url,
name='simple_url'),
url(r'^avatar_url/$',
views.avatar_url,
name='avatar_url'),
"",
url(r"^simple_url/$", views.simple_url, name="simple_url"),
url(r"^avatar_url/$", views.avatar_url, name="avatar_url"),
)

View file

@ -4,15 +4,17 @@ from django_downloadview import HTTPDownloadView
class SimpleURLDownloadView(HTTPDownloadView):
def get_url(self):
"""Return URL of hello-world.txt file on GitHub."""
return 'https://raw.githubusercontent.com' \
'/benoitbryon/django-downloadview' \
'/b7f660c5e3f37d918b106b02c5af7a887acc0111' \
'/demo/demoproject/download/fixtures/hello-world.txt'
return (
"https://raw.githubusercontent.com"
"/benoitbryon/django-downloadview"
"/b7f660c5e3f37d918b106b02c5af7a887acc0111"
"/demo/demoproject/download/fixtures/hello-world.txt"
)
class GithubAvatarDownloadView(HTTPDownloadView):
def get_url(self):
return 'https://avatars0.githubusercontent.com/u/235204'
return "https://avatars0.githubusercontent.com/u/235204"
simple_url = SimpleURLDownloadView.as_view()

View file

@ -12,32 +12,34 @@ from demoproject.lighttpd.views import storage, storage_dir
def setup_file():
if not os.path.exists(storage_dir):
os.makedirs(storage_dir)
storage.save('hello-world.txt', ContentFile(u'Hello world!\n'))
storage.save("hello-world.txt", ContentFile("Hello world!\n"))
class OptimizedByMiddlewareTestCase(django.test.TestCase):
def test_response(self):
"""'lighttpd:optimized_by_middleware' returns X-Sendfile response."""
setup_file()
url = reverse('lighttpd:optimized_by_middleware')
url = reverse("lighttpd:optimized_by_middleware")
response = self.client.get(url)
assert_x_sendfile(
self,
response,
content_type="text/plain; charset=utf-8",
basename="hello-world.txt",
file_path="/lighttpd-optimized-by-middleware/hello-world.txt")
file_path="/lighttpd-optimized-by-middleware/hello-world.txt",
)
class OptimizedByDecoratorTestCase(django.test.TestCase):
def test_response(self):
"""'lighttpd:optimized_by_decorator' returns X-Sendfile response."""
setup_file()
url = reverse('lighttpd:optimized_by_decorator')
url = reverse("lighttpd:optimized_by_decorator")
response = self.client.get(url)
assert_x_sendfile(
self,
response,
content_type="text/plain; charset=utf-8",
basename="hello-world.txt",
file_path="/lighttpd-optimized-by-decorator/hello-world.txt")
file_path="/lighttpd-optimized-by-decorator/hello-world.txt",
)

View file

@ -4,13 +4,16 @@ from django.conf.urls import url
from demoproject.compat import patterns
from demoproject.lighttpd import views
urlpatterns = patterns(
'demoproject.lighttpd.views',
url(r'^optimized-by-middleware/$',
"demoproject.lighttpd.views",
url(
r"^optimized-by-middleware/$",
views.optimized_by_middleware,
name='optimized_by_middleware'),
url(r'^optimized-by-decorator/$',
name="optimized_by_middleware",
),
url(
r"^optimized-by-decorator/$",
views.optimized_by_decorator,
name='optimized_by_decorator'),
name="optimized_by_decorator",
),
)

View file

@ -6,18 +6,19 @@ from django.core.files.storage import FileSystemStorage
from django_downloadview import StorageDownloadView
from django_downloadview.lighttpd import x_sendfile
storage_dir = os.path.join(settings.MEDIA_ROOT, 'lighttpd')
storage_dir = os.path.join(settings.MEDIA_ROOT, "lighttpd")
storage = FileSystemStorage(
location=storage_dir,
base_url=''.join([settings.MEDIA_URL, 'lighttpd/']))
location=storage_dir, base_url="".join([settings.MEDIA_URL, "lighttpd/"])
)
optimized_by_middleware = StorageDownloadView.as_view(storage=storage,
path='hello-world.txt')
optimized_by_middleware = StorageDownloadView.as_view(
storage=storage, path="hello-world.txt"
)
optimized_by_decorator = x_sendfile(
StorageDownloadView.as_view(storage=storage, path='hello-world.txt'),
StorageDownloadView.as_view(storage=storage, path="hello-world.txt"),
source_url=storage.base_url,
destination_dir='/lighttpd-optimized-by-decorator/')
destination_dir="/lighttpd-optimized-by-decorator/",
)

View file

@ -6,8 +6,7 @@ from django.core.management import execute_from_command_line
def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE",
"{package}.settings".format(package=__package__))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", f"{__package__}.settings")
execute_from_command_line(sys.argv)

View file

@ -12,14 +12,14 @@ from demoproject.nginx.views import storage, storage_dir
def setup_file():
if not os.path.exists(storage_dir):
os.makedirs(storage_dir)
storage.save('hello-world.txt', ContentFile(u'Hello world!\n'))
storage.save("hello-world.txt", ContentFile("Hello world!\n"))
class OptimizedByMiddlewareTestCase(django.test.TestCase):
def test_response(self):
"""'nginx:optimized_by_middleware' returns X-Accel response."""
setup_file()
url = reverse('nginx:optimized_by_middleware')
url = reverse("nginx:optimized_by_middleware")
response = self.client.get(url)
assert_x_accel_redirect(
self,
@ -30,14 +30,15 @@ class OptimizedByMiddlewareTestCase(django.test.TestCase):
redirect_url="/nginx-optimized-by-middleware/hello-world.txt",
expires=None,
with_buffering=None,
limit_rate=None)
limit_rate=None,
)
class OptimizedByDecoratorTestCase(django.test.TestCase):
def test_response(self):
"""'nginx:optimized_by_decorator' returns X-Accel response."""
setup_file()
url = reverse('nginx:optimized_by_decorator')
url = reverse("nginx:optimized_by_decorator")
response = self.client.get(url)
assert_x_accel_redirect(
self,
@ -48,4 +49,5 @@ class OptimizedByDecoratorTestCase(django.test.TestCase):
redirect_url="/nginx-optimized-by-decorator/hello-world.txt",
expires=None,
with_buffering=None,
limit_rate=None)
limit_rate=None,
)

View file

@ -5,13 +5,16 @@ from django.conf.urls import url
from demoproject.compat import patterns
from demoproject.nginx import views
urlpatterns = patterns(
'demoproject.nginx.views',
url(r'^optimized-by-middleware/$',
"demoproject.nginx.views",
url(
r"^optimized-by-middleware/$",
views.optimized_by_middleware,
name='optimized_by_middleware'),
url(r'^optimized-by-decorator/$',
name="optimized_by_middleware",
),
url(
r"^optimized-by-decorator/$",
views.optimized_by_decorator,
name='optimized_by_decorator'),
name="optimized_by_decorator",
),
)

View file

@ -6,17 +6,19 @@ from django.core.files.storage import FileSystemStorage
from django_downloadview import StorageDownloadView
from django_downloadview.nginx import x_accel_redirect
storage_dir = os.path.join(settings.MEDIA_ROOT, 'nginx')
storage = FileSystemStorage(location=storage_dir,
base_url=''.join([settings.MEDIA_URL, 'nginx/']))
storage_dir = os.path.join(settings.MEDIA_ROOT, "nginx")
storage = FileSystemStorage(
location=storage_dir, base_url="".join([settings.MEDIA_URL, "nginx/"])
)
optimized_by_middleware = StorageDownloadView.as_view(storage=storage,
path='hello-world.txt')
optimized_by_middleware = StorageDownloadView.as_view(
storage=storage, path="hello-world.txt"
)
optimized_by_decorator = x_accel_redirect(
StorageDownloadView.as_view(storage=storage, path='hello-world.txt'),
StorageDownloadView.as_view(storage=storage, path="hello-world.txt"),
source_url=storage.base_url,
destination_url='/nginx-optimized-by-decorator/')
destination_url="/nginx-optimized-by-decorator/",
)

View file

@ -3,6 +3,6 @@ from django.db import models
class Document(models.Model):
slug = models.SlugField()
file = models.FileField(upload_to='object')
another_file = models.FileField(upload_to='object-other')
file = models.FileField(upload_to="object")
another_file = models.FileField(upload_to="object-other")
basename = models.CharField(max_length=100)

View file

@ -1,29 +1,24 @@
from django.core.files.base import ContentFile
import django.test
from django_downloadview import temporary_media_root, assert_download_response
from django_downloadview import assert_download_response, temporary_media_root
from demoproject.compat import reverse
from demoproject.object.models import Document
# Fixtures.
slug = 'hello-world'
basename = 'hello-world.txt'
file_name = 'file.txt'
another_name = 'another_file.txt'
file_content = 'Hello world!\n'
another_content = 'Goodbye world!\n'
slug = "hello-world"
basename = "hello-world.txt"
file_name = "file.txt"
another_name = "another_file.txt"
file_content = "Hello world!\n"
another_content = "Goodbye world!\n"
def setup_document():
document = Document(slug=slug, basename=basename)
document.file.save(file_name,
ContentFile(file_content),
save=False)
document.another_file.save(another_name,
ContentFile(another_content),
save=False)
document.file.save(file_name, ContentFile(file_content), save=False)
document.another_file.save(another_name, ContentFile(another_content), save=False)
document.save()
return document
@ -33,13 +28,15 @@ class DefaultFileTestCase(django.test.TestCase):
def test_download_response(self):
"""'default_file' streams Document.file."""
setup_document()
url = reverse('object:default_file', kwargs={'slug': slug})
url = reverse("object:default_file", kwargs={"slug": slug})
response = self.client.get(url)
assert_download_response(self,
response,
content=file_content,
basename=file_name,
mime_type='text/plain')
assert_download_response(
self,
response,
content=file_content,
basename=file_name,
mime_type="text/plain",
)
class AnotherFileTestCase(django.test.TestCase):
@ -47,13 +44,15 @@ class AnotherFileTestCase(django.test.TestCase):
def test_download_response(self):
"""'another_file' streams Document.another_file."""
setup_document()
url = reverse('object:another_file', kwargs={'slug': slug})
url = reverse("object:another_file", kwargs={"slug": slug})
response = self.client.get(url)
assert_download_response(self,
response,
content=another_content,
basename=another_name,
mime_type='text/plain')
assert_download_response(
self,
response,
content=another_content,
basename=another_name,
mime_type="text/plain",
)
class DeserializedBasenameTestCase(django.test.TestCase):
@ -61,13 +60,15 @@ class DeserializedBasenameTestCase(django.test.TestCase):
def test_download_response(self):
"'deserialized_basename' streams Document.file with custom basename."
setup_document()
url = reverse('object:deserialized_basename', kwargs={'slug': slug})
url = reverse("object:deserialized_basename", kwargs={"slug": slug})
response = self.client.get(url)
assert_download_response(self,
response,
content=file_content,
basename=basename,
mime_type='text/plain')
assert_download_response(
self,
response,
content=file_content,
basename=basename,
mime_type="text/plain",
)
class InlineFileTestCase(django.test.TestCase):
@ -75,10 +76,12 @@ class InlineFileTestCase(django.test.TestCase):
def test_download_response(self):
"'inline_file_view' streams Document.file inline."
setup_document()
url = reverse('object:inline_file', kwargs={'slug': slug})
url = reverse("object:inline_file", kwargs={"slug": slug})
response = self.client.get(url)
assert_download_response(self,
response,
content=file_content,
mime_type='text/plain',
attachment=False)
assert_download_response(
self,
response,
content=file_content,
mime_type="text/plain",
attachment=False,
)

View file

@ -3,19 +3,26 @@ from django.conf.urls import url
from demoproject.compat import patterns
from demoproject.object import views
urlpatterns = patterns(
'',
url(r'^default-file/(?P<slug>[a-zA-Z0-9_-]+)/$',
"",
url(
r"^default-file/(?P<slug>[a-zA-Z0-9_-]+)/$",
views.default_file_view,
name='default_file'),
url(r'^another-file/(?P<slug>[a-zA-Z0-9_-]+)/$',
name="default_file",
),
url(
r"^another-file/(?P<slug>[a-zA-Z0-9_-]+)/$",
views.another_file_view,
name='another_file'),
url(r'^deserialized_basename/(?P<slug>[a-zA-Z0-9_-]+)/$',
name="another_file",
),
url(
r"^deserialized_basename/(?P<slug>[a-zA-Z0-9_-]+)/$",
views.deserialized_basename_view,
name='deserialized_basename'),
url(r'^inline-file/(?P<slug>[a-zA-Z0-9_-]+)/$',
name="deserialized_basename",
),
url(
r"^inline-file/(?P<slug>[a-zA-Z0-9_-]+)/$",
views.inline_file_view,
name='inline_file'),
name="inline_file",
),
)

View file

@ -2,22 +2,19 @@ from django_downloadview import ObjectDownloadView
from demoproject.object.models import Document
#: Serve ``file`` attribute of ``Document`` model.
default_file_view = ObjectDownloadView.as_view(model=Document)
#: Serve ``another_file`` attribute of ``Document`` model.
another_file_view = ObjectDownloadView.as_view(
model=Document,
file_field='another_file')
model=Document, file_field="another_file"
)
#: Serve ``file`` attribute of ``Document`` model, using client-side filename
#: from model.
deserialized_basename_view = ObjectDownloadView.as_view(
model=Document,
basename_field='basename')
model=Document, basename_field="basename"
)
#: Serve ``file`` attribute of ``Document`` model, inline (not as attachment).
inline_file_view = ObjectDownloadView.as_view(
model=Document,
attachment=False)
inline_file_view = ObjectDownloadView.as_view(model=Document, attachment=False)

View file

@ -8,22 +8,26 @@ from demoproject.compat import reverse
class StaticPathTestCase(django.test.TestCase):
def test_download_response(self):
"""'static_path' serves 'fixtures/hello-world.txt'."""
url = reverse('path:static_path')
url = reverse("path:static_path")
response = self.client.get(url)
assert_download_response(self,
response,
content='Hello world!\n',
basename='hello-world.txt',
mime_type='text/plain')
assert_download_response(
self,
response,
content="Hello world!\n",
basename="hello-world.txt",
mime_type="text/plain",
)
class DynamicPathTestCase(django.test.TestCase):
def test_download_response(self):
"""'dynamic_path' serves 'fixtures/{path}'."""
url = reverse('path:dynamic_path', kwargs={'path': 'hello-world.txt'})
url = reverse("path:dynamic_path", kwargs={"path": "hello-world.txt"})
response = self.client.get(url)
assert_download_response(self,
response,
content='Hello world!\n',
basename='hello-world.txt',
mime_type='text/plain')
assert_download_response(
self,
response,
content="Hello world!\n",
basename="hello-world.txt",
mime_type="text/plain",
)

View file

@ -3,13 +3,12 @@ from django.conf.urls import url
from demoproject.compat import patterns
from demoproject.path import views
urlpatterns = patterns(
'',
url(r'^static-path/$',
views.static_path,
name='static_path'),
url(r'^dynamic-path/(?P<path>[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{1,4})$',
"",
url(r"^static-path/$", views.static_path, name="static_path"),
url(
r"^dynamic-path/(?P<path>[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{1,4})$",
views.dynamic_path,
name='dynamic_path'),
name="dynamic_path",
),
)

View file

@ -2,13 +2,12 @@ import os
from django_downloadview import PathDownloadView
# Let's initialize some fixtures.
app_dir = os.path.dirname(os.path.abspath(__file__))
project_dir = os.path.dirname(app_dir)
fixtures_dir = os.path.join(project_dir, 'fixtures')
fixtures_dir = os.path.join(project_dir, "fixtures")
#: Path to a text file that says 'Hello world!'.
hello_world_path = os.path.join(fixtures_dir, 'hello-world.txt')
hello_world_path = os.path.join(fixtures_dir, "hello-world.txt")
#: Serve ``fixtures/hello-world.txt`` file.
static_path = PathDownloadView.as_view(path=hello_world_path)
@ -27,6 +26,7 @@ class DynamicPathDownloadView(PathDownloadView):
:class:`StorageDownloadView`
"""
def get_path(self):
"""Return path inside fixtures directory."""
# Get path from URL resolvers or as_view kwarg.

View file

@ -1,30 +1,27 @@
# -*- coding: utf-8 -*-
"""Django settings for django-downloadview demo project."""
from distutils.version import StrictVersion
import os
from django.utils.version import get_version
# Configure some relative directories.
demoproject_dir = os.path.dirname(os.path.abspath(__file__))
demo_dir = os.path.dirname(demoproject_dir)
root_dir = os.path.dirname(demo_dir)
data_dir = os.path.join(root_dir, 'var')
cfg_dir = os.path.join(root_dir, 'etc')
data_dir = os.path.join(root_dir, "var")
cfg_dir = os.path.join(root_dir, "etc")
# Mandatory settings.
ROOT_URLCONF = 'demoproject.urls'
WSGI_APPLICATION = 'demoproject.wsgi.application'
ROOT_URLCONF = "demoproject.urls"
WSGI_APPLICATION = "demoproject.wsgi.application"
# Database.
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(data_dir, 'db.sqlite'),
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": os.path.join(data_dir, "db.sqlite"),
}
}
@ -33,61 +30,61 @@ DATABASES = {
SECRET_KEY = "This is a secret made public on project's repository."
# Media and static files.
MEDIA_ROOT = os.path.join(data_dir, 'media')
MEDIA_URL = '/media/'
STATIC_ROOT = os.path.join(data_dir, 'static')
STATIC_URL = '/static/'
MEDIA_ROOT = os.path.join(data_dir, "media")
MEDIA_URL = "/media/"
STATIC_ROOT = os.path.join(data_dir, "static")
STATIC_URL = "/static/"
# Applications.
INSTALLED_APPS = (
# The actual django-downloadview demo.
'demoproject',
'demoproject.object', # Demo around ObjectDownloadView
'demoproject.storage', # Demo around StorageDownloadView
'demoproject.path', # Demo around PathDownloadView
'demoproject.http', # Demo around HTTPDownloadView
'demoproject.virtual', # Demo around VirtualDownloadView
'demoproject.nginx', # Sample optimizations for Nginx X-Accel.
'demoproject.apache', # Sample optimizations for Apache X-Sendfile.
'demoproject.lighttpd', # Sample optimizations for Lighttpd X-Sendfile.
"demoproject",
"demoproject.object", # Demo around ObjectDownloadView
"demoproject.storage", # Demo around StorageDownloadView
"demoproject.path", # Demo around PathDownloadView
"demoproject.http", # Demo around HTTPDownloadView
"demoproject.virtual", # Demo around VirtualDownloadView
"demoproject.nginx", # Sample optimizations for Nginx X-Accel.
"demoproject.apache", # Sample optimizations for Apache X-Sendfile.
"demoproject.lighttpd", # Sample optimizations for Lighttpd X-Sendfile.
# Standard Django applications.
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.sites",
"django.contrib.messages",
"django.contrib.staticfiles",
# Stuff that must be at the end.
'django_nose',
"django_nose",
)
# BEGIN middlewares
if StrictVersion(get_version()) >= StrictVersion('1.10'):
if StrictVersion(get_version()) >= StrictVersion("1.10"):
MIDDLEWARE = [
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django_downloadview.SmartDownloadMiddleware'
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django_downloadview.SmartDownloadMiddleware",
]
else:
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',
'django_downloadview.SmartDownloadMiddleware'
"django.middleware.common.CommonMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django_downloadview.SmartDownloadMiddleware",
]
# END middlewares
# Specific configuration for django_downloadview.SmartDownloadMiddleware.
# BEGIN backend
DOWNLOADVIEW_BACKEND = 'django_downloadview.nginx.XAccelRedirectMiddleware'
DOWNLOADVIEW_BACKEND = "django_downloadview.nginx.XAccelRedirectMiddleware"
# END backend
"""Could also be:
DOWNLOADVIEW_BACKEND = 'django_downloadview.apache.XSendfileMiddleware'
@ -97,70 +94,66 @@ DOWNLOADVIEW_BACKEND = 'django_downloadview.lighttpd.XSendfileMiddleware'
# BEGIN rules
DOWNLOADVIEW_RULES = [
{
'source_url': '/media/nginx/',
'destination_url': '/nginx-optimized-by-middleware/',
"source_url": "/media/nginx/",
"destination_url": "/nginx-optimized-by-middleware/",
},
]
# END rules
DOWNLOADVIEW_RULES += [
{
'source_url': '/media/apache/',
'destination_dir': '/apache-optimized-by-middleware/',
"source_url": "/media/apache/",
"destination_dir": "/apache-optimized-by-middleware/",
# Bypass global default backend with additional argument "backend".
# Notice that in general use case, ``DOWNLOADVIEW_BACKEND`` should be
# enough. Here, the django_downloadview demo project needs to
# demonstrate usage of several backends.
'backend': 'django_downloadview.apache.XSendfileMiddleware',
"backend": "django_downloadview.apache.XSendfileMiddleware",
},
{
'source_url': '/media/lighttpd/',
'destination_dir': '/lighttpd-optimized-by-middleware/',
"source_url": "/media/lighttpd/",
"destination_dir": "/lighttpd-optimized-by-middleware/",
# Bypass global default backend with additional argument "backend".
# Notice that in general use case, ``DOWNLOADVIEW_BACKEND`` should be
# enough. Here, the django_downloadview demo project needs to
# demonstrate usage of several backends.
'backend': 'django_downloadview.lighttpd.XSendfileMiddleware',
"backend": "django_downloadview.lighttpd.XSendfileMiddleware",
},
]
# Test/development settings.
DEBUG = True
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
TEST_RUNNER = "django_nose.NoseTestSuiteRunner"
NOSE_ARGS = [
'--verbosity=2',
'--no-path-adjustment',
'--nocapture',
'--all-modules',
'--with-coverage',
'--with-doctest',
"--verbosity=2",
"--no-path-adjustment",
"--nocapture",
"--all-modules",
"--with-coverage",
"--with-doctest",
]
if StrictVersion(get_version()) >= StrictVersion('1.8'):
if StrictVersion(get_version()) >= StrictVersion("1.8"):
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(os.path.dirname(__file__), "templates"),
],
'OPTIONS': {
'debug': DEBUG,
'context_processors': [
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(os.path.dirname(__file__), "templates")],
"OPTIONS": {
"debug": DEBUG,
"context_processors": [
# Insert your TEMPLATE_CONTEXT_PROCESSORS here or use this
# list if you haven't customized them:
'django.contrib.auth.context_processors.auth',
'django.template.context_processors.debug',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.contrib.messages.context_processors.messages",
],
},
},
]
else:
TEMPLATE_DEBUG = DEBUG
TEMPLATE_DIRS = (
os.path.join(os.path.dirname(__file__), "templates"),
)
TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), "templates"),)

View file

@ -1,4 +1,3 @@
from django.core.files.storage import FileSystemStorage
storage = FileSystemStorage()

View file

@ -5,15 +5,17 @@ from django.core.files.base import ContentFile
from django.http.response import HttpResponseNotModified
import django.test
from django_downloadview import assert_download_response, temporary_media_root
from django_downloadview import setup_view
from django_downloadview import (
assert_download_response,
setup_view,
temporary_media_root,
)
from demoproject.compat import reverse
from demoproject.storage import views
# Fixtures.
file_content = 'Hello world!\n'
file_content = "Hello world!\n"
def setup_file(path):
@ -24,44 +26,48 @@ class StaticPathTestCase(django.test.TestCase):
@temporary_media_root()
def test_download_response(self):
"""'storage:static_path' streams file by path."""
setup_file('1.txt')
url = reverse('storage:static_path', kwargs={'path': '1.txt'})
setup_file("1.txt")
url = reverse("storage:static_path", kwargs={"path": "1.txt"})
response = self.client.get(url)
assert_download_response(self,
response,
content=file_content,
basename='1.txt',
mime_type='text/plain')
assert_download_response(
self,
response,
content=file_content,
basename="1.txt",
mime_type="text/plain",
)
@temporary_media_root()
def test_not_modified_download_response(self):
"""'storage:static_path' sends not modified response if unmodified."""
setup_file('1.txt')
url = reverse('storage:static_path', kwargs={'path': '1.txt'})
setup_file("1.txt")
url = reverse("storage:static_path", kwargs={"path": "1.txt"})
year = datetime.date.today().year + 4
response = self.client.get(
url,
HTTP_IF_MODIFIED_SINCE='Sat, 29 Oct {year} 19:43:31 GMT'.format(
year=datetime.date.today().year + 4)
url, HTTP_IF_MODIFIED_SINCE=f"Sat, 29 Oct {year} 19:43:31 GMT",
)
self.assertTrue(isinstance(response, HttpResponseNotModified))
@temporary_media_root()
def test_modified_since_download_response(self):
"""'storage:static_path' streams file if modified."""
setup_file('1.txt')
url = reverse('storage:static_path', kwargs={'path': '1.txt'})
setup_file("1.txt")
url = reverse("storage:static_path", kwargs={"path": "1.txt"})
response = self.client.get(
url,
HTTP_IF_MODIFIED_SINCE='Sat, 29 Oct 1980 19:43:31 GMT')
assert_download_response(self,
response,
content=file_content,
basename='1.txt',
mime_type='text/plain')
url, HTTP_IF_MODIFIED_SINCE="Sat, 29 Oct 1980 19:43:31 GMT"
)
assert_download_response(
self,
response,
content=file_content,
basename="1.txt",
mime_type="text/plain",
)
class DynamicPathIntegrationTestCase(django.test.TestCase):
"""Integration tests around ``storage:dynamic_path`` URL."""
@temporary_media_root()
def test_download_response(self):
"""'dynamic_path' streams file by generated path.
@ -74,18 +80,21 @@ class DynamicPathIntegrationTestCase(django.test.TestCase):
file in storage.
"""
setup_file('1.TXT')
url = reverse('storage:dynamic_path', kwargs={'path': '1.txt'})
setup_file("1.TXT")
url = reverse("storage:dynamic_path", kwargs={"path": "1.txt"})
response = self.client.get(url)
assert_download_response(self,
response,
content=file_content,
basename='1.TXT',
mime_type='text/plain')
assert_download_response(
self,
response,
content=file_content,
basename="1.TXT",
mime_type="text/plain",
)
class DynamicPathUnitTestCase(unittest.TestCase):
"""Unit tests around ``views.DynamicStorageDownloadView``."""
def test_get_path(self):
"""DynamicStorageDownloadView.get_path() returns uppercase path.
@ -97,8 +106,10 @@ class DynamicPathUnitTestCase(unittest.TestCase):
URL works. It targets only custom ``DynamicStorageDownloadView`` class.
"""
view = setup_view(views.DynamicStorageDownloadView(),
django.test.RequestFactory().get('/fake-url'),
path='dummy path')
view = setup_view(
views.DynamicStorageDownloadView(),
django.test.RequestFactory().get("/fake-url"),
path="dummy path",
)
path = view.get_path()
self.assertEqual(path, 'DUMMY PATH')
self.assertEqual(path, "DUMMY PATH")

View file

@ -3,13 +3,16 @@ from django.conf.urls import url
from demoproject.compat import patterns
from demoproject.storage import views
urlpatterns = patterns(
'',
url(r'^static-path/(?P<path>[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{1,4})$',
"",
url(
r"^static-path/(?P<path>[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{1,4})$",
views.static_path,
name='static_path'),
url(r'^dynamic-path/(?P<path>[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{1,4})$',
name="static_path",
),
url(
r"^dynamic-path/(?P<path>[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{1,4})$",
views.dynamic_path,
name='dynamic_path'),
name="dynamic_path",
),
)

View file

@ -2,7 +2,6 @@ from django.core.files.storage import FileSystemStorage
from django_downloadview import StorageDownloadView
storage = FileSystemStorage()
@ -12,6 +11,7 @@ static_path = StorageDownloadView.as_view(storage=storage)
class DynamicStorageDownloadView(StorageDownloadView):
"""Serve file of storage by path.upper()."""
def get_path(self):
"""Return uppercase path."""
return super(DynamicStorageDownloadView, self).get_path().upper()

View file

@ -1,14 +1,15 @@
# coding=utf8
"""Test suite for demoproject.download."""
from demoproject.compat import reverse
from django.test import TestCase
from demoproject.compat import reverse
class HomeViewTestCase(TestCase):
"""Test homepage."""
def test_get(self):
"""Homepage returns HTTP 200."""
home_url = reverse('home')
home_url = reverse("home")
response = self.client.get(home_url)
self.assertEqual(response.status_code, 200)

View file

@ -1,46 +1,47 @@
from django.conf.urls import url
from django.views.generic import TemplateView
from demoproject.compat import patterns, include
from demoproject.compat import include, patterns
home = TemplateView.as_view(template_name='home.html')
home = TemplateView.as_view(template_name="home.html")
urlpatterns = patterns(
'',
"",
# ObjectDownloadView.
url(r'^object/', include('demoproject.object.urls',
app_name='object',
namespace='object')),
url(
r"^object/",
include("demoproject.object.urls", app_name="object", namespace="object"),
),
# StorageDownloadView.
url(r'^storage/', include('demoproject.storage.urls',
app_name='storage',
namespace='storage')),
url(
r"^storage/",
include("demoproject.storage.urls", app_name="storage", namespace="storage"),
),
# PathDownloadView.
url(r'^path/', include('demoproject.path.urls',
app_name='path',
namespace='path')),
url(r"^path/", include("demoproject.path.urls", app_name="path", namespace="path")),
# HTTPDownloadView.
url(r'^http/', include('demoproject.http.urls',
app_name='http',
namespace='http')),
url(r"^http/", include("demoproject.http.urls", app_name="http", namespace="http")),
# VirtualDownloadView.
url(r'^virtual/', include('demoproject.virtual.urls',
app_name='virtual',
namespace='virtual')),
url(
r"^virtual/",
include("demoproject.virtual.urls", app_name="virtual", namespace="virtual"),
),
# Nginx optimizations.
url(r'^nginx/', include('demoproject.nginx.urls',
app_name='nginx',
namespace='nginx')),
url(
r"^nginx/",
include("demoproject.nginx.urls", app_name="nginx", namespace="nginx"),
),
# Apache optimizations.
url(r'^apache/', include('demoproject.apache.urls',
app_name='apache',
namespace='apache')),
url(
r"^apache/",
include("demoproject.apache.urls", app_name="apache", namespace="apache"),
),
# Lighttpd optimizations.
url(r'^lighttpd/', include('demoproject.lighttpd.urls',
app_name='lighttpd',
namespace='lighttpd')),
url(
r"^lighttpd/",
include("demoproject.lighttpd.urls", app_name="lighttpd", namespace="lighttpd"),
),
# An informative homepage.
url(r'$', home, name='home')
url(r"$", home, name="home"),
)

View file

@ -8,34 +8,40 @@ from demoproject.compat import reverse
class TextTestCase(django.test.TestCase):
def test_download_response(self):
"""'virtual:text' serves 'hello-world.txt' from unicode."""
url = reverse('virtual:text')
url = reverse("virtual:text")
response = self.client.get(url)
assert_download_response(self,
response,
content='Hello world!\n',
basename='hello-world.txt',
mime_type='text/plain')
assert_download_response(
self,
response,
content="Hello world!\n",
basename="hello-world.txt",
mime_type="text/plain",
)
class StringIOTestCase(django.test.TestCase):
def test_download_response(self):
"""'virtual:stringio' serves 'hello-world.txt' from stringio."""
url = reverse('virtual:stringio')
url = reverse("virtual:stringio")
response = self.client.get(url)
assert_download_response(self,
response,
content='Hello world!\n',
basename='hello-world.txt',
mime_type='text/plain')
assert_download_response(
self,
response,
content="Hello world!\n",
basename="hello-world.txt",
mime_type="text/plain",
)
class GeneratedTestCase(django.test.TestCase):
def test_download_response(self):
"""'virtual:generated' serves 'hello-world.txt' from generator."""
url = reverse('virtual:generated')
url = reverse("virtual:generated")
response = self.client.get(url)
assert_download_response(self,
response,
content='Hello world!\n',
basename='hello-world.txt',
mime_type='text/plain')
assert_download_response(
self,
response,
content="Hello world!\n",
basename="hello-world.txt",
mime_type="text/plain",
)

View file

@ -3,16 +3,9 @@ from django.conf.urls import url
from demoproject.compat import patterns
from demoproject.virtual import views
urlpatterns = patterns(
'',
url(r'^text/$',
views.TextDownloadView.as_view(),
name='text'),
url(r'^stringio/$',
views.StringIODownloadView.as_view(),
name='stringio'),
url(r'^gerenated/$',
views.GeneratedDownloadView.as_view(),
name='generated'),
"",
url(r"^text/$", views.TextDownloadView.as_view(), name="text"),
url(r"^stringio/$", views.StringIODownloadView.as_view(), name="stringio"),
url(r"^gerenated/$", views.GeneratedDownloadView.as_view(), name="generated"),
)

View file

@ -2,32 +2,30 @@ from io import StringIO
from django.core.files.base import ContentFile
from django_downloadview import VirtualDownloadView
from django_downloadview import VirtualFile
from django_downloadview import TextIteratorIO
from django_downloadview import TextIteratorIO, VirtualDownloadView, VirtualFile
class TextDownloadView(VirtualDownloadView):
def get_file(self):
"""Return :class:`django.core.files.base.ContentFile` object."""
return ContentFile(b"Hello world!\n", name='hello-world.txt')
return ContentFile(b"Hello world!\n", name="hello-world.txt")
class StringIODownloadView(VirtualDownloadView):
def get_file(self):
"""Return wrapper on ``six.StringIO`` object."""
file_obj = StringIO(u"Hello world!\n")
return VirtualFile(file_obj, name='hello-world.txt')
file_obj = StringIO("Hello world!\n")
return VirtualFile(file_obj, name="hello-world.txt")
def generate_hello():
yield u'Hello '
yield u'world!'
yield u'\n'
yield "Hello "
yield "world!"
yield "\n"
class GeneratedDownloadView(VirtualDownloadView):
def get_file(self):
"""Return wrapper on ``StringIteratorIO`` object."""
file_obj = TextIteratorIO(generate_hello())
return VirtualFile(file_obj, name='hello-world.txt')
return VirtualFile(file_obj, name="hello-world.txt")

View file

@ -16,7 +16,6 @@ import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "%s.settings" % __package__)
# This application object is used by any WSGI server configured to use this

View file

@ -1,48 +1,46 @@
# -*- coding: utf-8 -*-
"""Python packaging."""
import os
from setuptools import setup
here = os.path.abspath(os.path.dirname(__file__))
project_root = os.path.dirname(here)
NAME = 'django-downloadview-demo'
DESCRIPTION = 'Serve files with Django and reverse-proxies.'
README = open(os.path.join(here, 'README.rst')).read()
VERSION = open(os.path.join(project_root, 'VERSION')).read().strip()
AUTHOR = u'Benoît Bryon'
EMAIL = u'benoit@marmelune.net'
URL = 'https://django-downloadview.readthedocs.io/'
CLASSIFIERS = ['Development Status :: 5 - Production/Stable',
'License :: OSI Approved :: BSD License',
'Programming Language :: Python :: 2.7',
'Framework :: Django']
NAME = "django-downloadview-demo"
DESCRIPTION = "Serve files with Django and reverse-proxies."
README = open(os.path.join(here, "README.rst")).read()
VERSION = open(os.path.join(project_root, "VERSION")).read().strip()
AUTHOR = "Benoît Bryon"
EMAIL = "benoit@marmelune.net"
URL = "https://django-downloadview.readthedocs.io/"
CLASSIFIERS = [
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: BSD License",
"Programming Language :: Python :: 2.7",
"Framework :: Django",
]
KEYWORDS = []
PACKAGES = ['demoproject']
REQUIREMENTS = [
'django-downloadview',
'django-nose']
ENTRY_POINTS = {
'console_scripts': ['demo = demoproject.manage:main']
}
PACKAGES = ["demoproject"]
REQUIREMENTS = ["django-downloadview", "django-nose"]
ENTRY_POINTS = {"console_scripts": ["demo = demoproject.manage:main"]}
if __name__ == '__main__': # Don't run setup() when we import this module.
setup(name=NAME,
version=VERSION,
description=DESCRIPTION,
long_description=README,
classifiers=CLASSIFIERS,
keywords=' '.join(KEYWORDS),
author=AUTHOR,
author_email=EMAIL,
url=URL,
license='BSD',
packages=PACKAGES,
include_package_data=True,
zip_safe=False,
install_requires=REQUIREMENTS,
entry_points=ENTRY_POINTS)
if __name__ == "__main__": # Don't run setup() when we import this module.
setup(
name=NAME,
version=VERSION,
description=DESCRIPTION,
long_description=README,
classifiers=CLASSIFIERS,
keywords=" ".join(KEYWORDS),
author=AUTHOR,
author_email=EMAIL,
url=URL,
license="BSD",
packages=PACKAGES,
include_package_data=True,
zip_safe=False,
install_requires=REQUIREMENTS,
entry_points=ENTRY_POINTS,
)

View file

@ -1,10 +1,7 @@
"""Serve files with Django and reverse proxies."""
from django_downloadview.api import * # NoQA
import pkg_resources
#: Module version, as defined in PEP-0396.
__version__ = pkg_resources.get_distribution(__package__.replace('-', '_')).version
# API shortcuts.
from django_downloadview.api import * # NoQA
__version__ = pkg_resources.get_distribution(__package__.replace("-", "_")).version

View file

@ -7,6 +7,6 @@ Apache optimizations </optimizations/apache>`.
"""
# API shortcuts.
from django_downloadview.apache.decorators import x_sendfile # NoQA
from django_downloadview.apache.middlewares import XSendfileMiddleware # NoQA
from django_downloadview.apache.response import XSendfileResponse # NoQA
from django_downloadview.apache.tests import assert_x_sendfile # NoQA
from django_downloadview.apache.middlewares import XSendfileMiddleware # NoQA

View file

@ -1,6 +1,6 @@
"""Decorators to apply Apache X-Sendfile on a specific view."""
from django_downloadview.decorators import DownloadDecorator
from django_downloadview.apache.middlewares import XSendfileMiddleware
from django_downloadview.decorators import DownloadDecorator
def x_sendfile(view_func, *args, **kwargs):

View file

@ -1,6 +1,8 @@
from django_downloadview.apache.response import XSendfileResponse
from django_downloadview.middlewares import (ProxiedDownloadMiddleware,
NoRedirectionMatch)
from django_downloadview.middlewares import (
NoRedirectionMatch,
ProxiedDownloadMiddleware,
)
class XSendfileMiddleware(ProxiedDownloadMiddleware):
@ -12,9 +14,10 @@ class XSendfileMiddleware(ProxiedDownloadMiddleware):
:py:class:`django_downloadview.decorators.DownloadDecorator`.
"""
def __init__(self,
get_response=None,
source_dir=None, source_url=None, destination_dir=None):
def __init__(
self, get_response=None, source_dir=None, source_url=None, destination_dir=None
):
"""Constructor."""
super(XSendfileMiddleware, self).__init__(
get_response, source_dir, source_url, destination_dir
@ -26,7 +29,9 @@ class XSendfileMiddleware(ProxiedDownloadMiddleware):
redirect_url = self.get_redirect_url(response)
except NoRedirectionMatch:
return response
return XSendfileResponse(file_path=redirect_url,
content_type=response['Content-Type'],
basename=response.basename,
attachment=response.attachment)
return XSendfileResponse(
file_path=redirect_url,
content_type=response["Content-Type"],
basename=response.basename,
attachment=response.attachment,
)

View file

@ -1,16 +1,16 @@
"""Apache's specific responses."""
import os.path
from django_downloadview.response import (ProxiedDownloadResponse,
content_disposition)
from django_downloadview.response import ProxiedDownloadResponse, content_disposition
class XSendfileResponse(ProxiedDownloadResponse):
"Delegates serving file to Apache via X-Sendfile header."
def __init__(self, file_path, content_type, basename=None, attachment=True):
"""Return a HttpResponse with headers for Apache X-Sendfile."""
super(XSendfileResponse, self).__init__(content_type=content_type)
if attachment:
self.basename = basename or os.path.basename(file_path)
self['Content-Disposition'] = content_disposition(self.basename)
self['X-Sendfile'] = file_path
self["Content-Disposition"] = content_disposition(self.basename)
self["X-Sendfile"] = file_path

View file

@ -7,6 +7,7 @@ class XSendfileValidator(object):
See also :py:func:`assert_x_sendfile` shortcut function.
"""
def __call__(self, test_case, response, **assertions):
"""Assert that ``response`` is a valid X-Sendfile response.
@ -22,7 +23,7 @@ class XSendfileValidator(object):
"""
self.assert_x_sendfile_response(test_case, response)
for key, value in assertions.items():
assert_func = getattr(self, 'assert_%s' % key)
assert_func = getattr(self, "assert_%s" % key)
assert_func(test_case, response, value)
def assert_x_sendfile_response(self, test_case, response):
@ -32,15 +33,15 @@ class XSendfileValidator(object):
test_case.assertEqual(response.basename, value)
def assert_content_type(self, test_case, response, value):
test_case.assertEqual(response['Content-Type'], value)
test_case.assertEqual(response["Content-Type"], value)
def assert_file_path(self, test_case, response, value):
test_case.assertEqual(response['X-Sendfile'], value)
test_case.assertEqual(response["X-Sendfile"], value)
def assert_attachment(self, test_case, response, value):
header = 'Content-Disposition'
header = "Content-Disposition"
if value:
test_case.assertTrue(response[header].startswith('attachment'))
test_case.assertTrue(response[header].startswith("attachment"))
else:
test_case.assertFalse(header in response)

View file

@ -1,26 +1,28 @@
# flake8: noqa
"""Declaration of API shortcuts."""
from django_downloadview.io import (BytesIteratorIO, # NoQA
TextIteratorIO)
from django_downloadview.files import (StorageFile, # NoQA
VirtualFile,
HTTPFile)
from django_downloadview.response import (DownloadResponse, # NoQA
ProxiedDownloadResponse)
from django_downloadview.middlewares import (BaseDownloadMiddleware, # NoQA
DownloadDispatcherMiddleware,
SmartDownloadMiddleware)
from django_downloadview.views import (PathDownloadView, # NoQA
ObjectDownloadView,
StorageDownloadView,
HTTPDownloadView,
VirtualDownloadView,
BaseDownloadView,
DownloadMixin)
from django_downloadview.shortcuts import sendfile # NoQA
from django_downloadview.test import (assert_download_response, # NoQA
setup_view,
temporary_media_root)
from django_downloadview.files import HTTPFile, StorageFile, VirtualFile
from django_downloadview.io import BytesIteratorIO, TextIteratorIO
from django_downloadview.middlewares import (
BaseDownloadMiddleware,
DownloadDispatcherMiddleware,
SmartDownloadMiddleware,
)
from django_downloadview.response import DownloadResponse, ProxiedDownloadResponse
from django_downloadview.shortcuts import sendfile
from django_downloadview.test import (
assert_download_response,
setup_view,
temporary_media_root,
)
from django_downloadview.views import (
BaseDownloadView,
DownloadMixin,
HTTPDownloadView,
ObjectDownloadView,
PathDownloadView,
StorageDownloadView,
VirtualDownloadView,
)
# Backward compatibility.
StringIteratorIO = TextIteratorIO

View file

@ -17,16 +17,18 @@ class DownloadDecorator(object):
method is applied on response.
"""
def __init__(self, middleware_factory):
"""Create a download view decorator."""
self.middleware_factory = middleware_factory
def __call__(self, view_func, *middleware_args, **middleware_kwargs):
"""Return ``view_func`` decorated with response middleware."""
def decorated(request, *view_args, **view_kwargs):
"""Return view's response modified by middleware."""
response = view_func(request, *view_args, **view_kwargs)
middleware = self.middleware_factory(*middleware_args,
**middleware_kwargs)
middleware = self.middleware_factory(*middleware_args, **middleware_kwargs)
return middleware.process_response(request, response)
return decorated

View file

@ -5,10 +5,10 @@ from urllib.parse import urlparse
from django.core.files.base import File
from django.utils.encoding import force_bytes
import requests
from django_downloadview.io import BytesIteratorIO
import requests
class StorageFile(File):
"""A file in a Django storage.
@ -17,6 +17,7 @@ class StorageFile(File):
but unrelated to model instance.
"""
def __init__(self, storage, name, file=None):
"""Constructor.
@ -33,8 +34,8 @@ class StorageFile(File):
def _get_file(self):
"""Getter for :py:attr:``file`` property."""
if not hasattr(self, '_file') or self._file is None:
self._file = self.storage.open(self.name, 'rb')
if not hasattr(self, "_file") or self._file is None:
self._file = self.storage.open(self.name, "rb")
return self._file
def _set_file(self, file):
@ -48,7 +49,7 @@ class StorageFile(File):
#: Required by django.core.files.utils.FileProxy.
file = property(_get_file, _set_file, _del_file)
def open(self, mode='rb'):
def open(self, mode="rb"):
"""Retrieves the specified file from storage and return open() result.
Proxy to self.storage.open(self.name, mode).
@ -152,7 +153,8 @@ class StorageFile(File):
class VirtualFile(File):
"""Wrapper for files that live in memory."""
def __init__(self, file=None, name=u'', url='', size=None):
def __init__(self, file=None, name="", url="", size=None):
"""Constructor.
file:
@ -203,7 +205,7 @@ class VirtualFile(File):
# If this is the end of a line, yield
# otherwise, wait for the next round
if line[-1] in ('\n', '\r'):
if line[-1] in ("\n", "\r"):
yield line
else:
buffer_ = line
@ -222,19 +224,19 @@ class HTTPFile(File):
Always sets "stream=True" in requests kwargs.
"""
def __init__(self, request_factory=requests.get, url='', name=u'',
**kwargs):
def __init__(self, request_factory=requests.get, url="", name="", **kwargs):
self.request_factory = request_factory
self.url = url
if name is None:
parts = urlparse(url)
if parts.path: # Name from path.
self.name = parts.path.strip('/').rsplit('/', 1)[-1]
self.name = parts.path.strip("/").rsplit("/", 1)[-1]
else: # Name from domain.
self.name = parts.netloc
else:
self.name = name
kwargs['stream'] = True
kwargs["stream"] = True
self.request_kwargs = kwargs
@property
@ -242,8 +244,7 @@ class HTTPFile(File):
try:
return self._request
except AttributeError:
self._request = self.request_factory(self.url,
**self.request_kwargs)
self._request = self.request_factory(self.url, **self.request_kwargs)
return self._request
@property
@ -262,9 +263,9 @@ class HTTPFile(File):
Reads response's "content-length" header.
"""
return self.request.headers['Content-Length']
return self.request.headers["Content-Length"]
@property
def content_type(self):
"""Return content type of the file (from original response)."""
return self.request.headers['Content-Type']
return self.request.headers["Content-Type"]

View file

@ -1,7 +1,7 @@
"""Low-level IO operations, for use with file wrappers."""
import io
from django.utils.encoding import force_text, force_bytes
from django.utils.encoding import force_bytes, force_text
class TextIteratorIO(io.TextIOBase):
@ -13,12 +13,13 @@ class TextIteratorIO(io.TextIOBase):
* https://gist.github.com/anacrolix/3788413
"""
def __init__(self, iterator):
#: Iterator/generator for content.
self._iter = iterator
#: Internal buffer.
self._left = u''
self._left = ""
def readable(self):
return True
@ -33,7 +34,7 @@ class TextIteratorIO(io.TextIOBase):
# Make sure we handle text.
self._left = force_text(self._left)
ret = self._left[:n]
self._left = self._left[len(ret):]
self._left = self._left[len(ret) :]
return ret
def read(self, n=None):
@ -52,24 +53,24 @@ class TextIteratorIO(io.TextIOBase):
break
n -= len(m)
chunks.append(m)
return u''.join(chunks)
return "".join(chunks)
def readline(self):
chunks = []
while True:
i = self._left.find(u'\n')
i = self._left.find("\n")
if i == -1:
chunks.append(self._left)
try:
self._left = next(self._iter)
except StopIteration:
self._left = u''
self._left = ""
break
else:
chunks.append(self._left[:i + 1])
self._left = self._left[i + 1:]
chunks.append(self._left[: i + 1])
self._left = self._left[i + 1 :]
break
return u''.join(chunks)
return "".join(chunks)
class BytesIteratorIO(io.BytesIO):
@ -81,12 +82,13 @@ class BytesIteratorIO(io.BytesIO):
* https://gist.github.com/anacrolix/3788413
"""
def __init__(self, iterator):
#: Iterator/generator for content.
self._iter = iterator
#: Internal buffer.
self._left = b''
self._left = b""
def readable(self):
return True
@ -101,7 +103,7 @@ class BytesIteratorIO(io.BytesIO):
# Make sure we handle text.
self._left = force_bytes(self._left)
ret = self._left[:n]
self._left = self._left[len(ret):]
self._left = self._left[len(ret) :]
return ret
def read(self, n=None):
@ -120,21 +122,21 @@ class BytesIteratorIO(io.BytesIO):
break
n -= len(m)
chunks.append(m)
return b''.join(chunks)
return b"".join(chunks)
def readline(self):
chunks = []
while True:
i = self._left.find(b'\n')
i = self._left.find(b"\n")
if i == -1:
chunks.append(self._left)
try:
self._left = next(self._iter)
except StopIteration:
self._left = b''
self._left = b""
break
else:
chunks.append(self._left[:i + 1])
self._left = self._left[i + 1:]
chunks.append(self._left[: i + 1])
self._left = self._left[i + 1 :]
break
return b''.join(chunks)
return b"".join(chunks)

View file

@ -8,6 +8,6 @@ See also `documentation of X-Sendfile for Lighttpd
"""
# API shortcuts.
from django_downloadview.lighttpd.decorators import x_sendfile # NoQA
from django_downloadview.lighttpd.middlewares import XSendfileMiddleware # NoQA
from django_downloadview.lighttpd.response import XSendfileResponse # NoQA
from django_downloadview.lighttpd.tests import assert_x_sendfile # NoQA
from django_downloadview.lighttpd.middlewares import XSendfileMiddleware # NoQA

View file

@ -1,6 +1,8 @@
from django_downloadview.lighttpd.response import XSendfileResponse
from django_downloadview.middlewares import (ProxiedDownloadMiddleware,
NoRedirectionMatch)
from django_downloadview.middlewares import (
NoRedirectionMatch,
ProxiedDownloadMiddleware,
)
class XSendfileMiddleware(ProxiedDownloadMiddleware):
@ -12,9 +14,10 @@ class XSendfileMiddleware(ProxiedDownloadMiddleware):
:py:class:`django_downloadview.decorators.DownloadDecorator`.
"""
def __init__(self,
get_response=None,
source_dir=None, source_url=None, destination_dir=None):
def __init__(
self, get_response=None, source_dir=None, source_url=None, destination_dir=None
):
"""Constructor."""
super(XSendfileMiddleware, self).__init__(
get_response, source_dir, source_url, destination_dir
@ -26,7 +29,9 @@ class XSendfileMiddleware(ProxiedDownloadMiddleware):
redirect_url = self.get_redirect_url(response)
except NoRedirectionMatch:
return response
return XSendfileResponse(file_path=redirect_url,
content_type=response['Content-Type'],
basename=response.basename,
attachment=response.attachment)
return XSendfileResponse(
file_path=redirect_url,
content_type=response["Content-Type"],
basename=response.basename,
attachment=response.attachment,
)

View file

@ -1,17 +1,16 @@
"""Lighttpd's specific responses."""
import os.path
from django_downloadview.response import (ProxiedDownloadResponse,
content_disposition)
from django_downloadview.response import ProxiedDownloadResponse, content_disposition
class XSendfileResponse(ProxiedDownloadResponse):
"Delegates serving file to Lighttpd via X-Sendfile header."
def __init__(self, file_path, content_type, basename=None,
attachment=True):
def __init__(self, file_path, content_type, basename=None, attachment=True):
"""Return a HttpResponse with headers for Lighttpd X-Sendfile."""
super(XSendfileResponse, self).__init__(content_type=content_type)
if attachment:
self.basename = basename or os.path.basename(file_path)
self['Content-Disposition'] = content_disposition(self.basename)
self['X-Sendfile'] = file_path
self["Content-Disposition"] = content_disposition(self.basename)
self["X-Sendfile"] = file_path

View file

@ -8,6 +8,7 @@ class XSendfileValidator(django_downloadview.apache.tests.XSendfileValidator):
See also :py:func:`assert_x_sendfile` shortcut function.
"""
def assert_x_sendfile_response(self, test_case, response):
test_case.assertTrue(isinstance(response, XSendfileResponse))

View file

@ -4,23 +4,24 @@ Download middlewares capture :py:class:`django_downloadview.DownloadResponse`
responses and may replace them with optimized download responses.
"""
import copy
import collections
import copy
import os
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django_downloadview.response import DownloadResponse
from django_downloadview.utils import import_member
try:
from django.utils.deprecation import MiddlewareMixin
except ImportError:
class MiddlewareMixin(object):
def __init__(self, get_response=None):
super(MiddlewareMixin, self).__init__()
from django_downloadview.response import DownloadResponse
from django_downloadview.utils import import_member
#: Sentinel value to detect whether configuration is to be loaded from Django
#: settings or not.
@ -43,6 +44,7 @@ class BaseDownloadMiddleware(MiddlewareMixin):
Subclasses **must** implement :py:meth:`process_download_response` method.
"""
def is_download_response(self, response):
"""Return True if ``response`` can be considered as a file download.
@ -66,6 +68,7 @@ class BaseDownloadMiddleware(MiddlewareMixin):
class RealDownloadMiddleware(BaseDownloadMiddleware):
"""Download middleware that cannot handle virtual files."""
def is_download_response(self, response):
"""Return True for DownloadResponse, except for "virtual" files.
@ -86,6 +89,7 @@ class RealDownloadMiddleware(BaseDownloadMiddleware):
class DownloadDispatcherMiddleware(BaseDownloadMiddleware):
"Download middleware that dispatches job to several middleware instances."
def __init__(self, get_response=None, middlewares=AUTO_CONFIGURE):
super(DownloadDispatcherMiddleware, self).__init__(get_response)
#: List of children middlewares.
@ -96,9 +100,9 @@ class DownloadDispatcherMiddleware(BaseDownloadMiddleware):
def auto_configure_middlewares(self):
"""Populate :attr:`middlewares` from
``settings.DOWNLOADVIEW_MIDDLEWARES``."""
for (key, import_string, kwargs) in getattr(settings,
'DOWNLOADVIEW_MIDDLEWARES',
[]):
for (key, import_string, kwargs) in getattr(
settings, "DOWNLOADVIEW_MIDDLEWARES", []
):
factory = import_member(import_string)
middleware = factory(**kwargs)
self.middlewares.append((key, middleware))
@ -112,10 +116,13 @@ class DownloadDispatcherMiddleware(BaseDownloadMiddleware):
class SmartDownloadMiddleware(BaseDownloadMiddleware):
"""Easy to configure download middleware."""
def __init__(self,
get_response=None,
backend_factory=AUTO_CONFIGURE,
backend_options=AUTO_CONFIGURE):
def __init__(
self,
get_response=None,
backend_factory=AUTO_CONFIGURE,
backend_options=AUTO_CONFIGURE,
):
"""Constructor."""
super(SmartDownloadMiddleware, self).__init__(get_response)
#: :class:`DownloadDispatcher` instance that can hold multiple
@ -137,8 +144,9 @@ class SmartDownloadMiddleware(BaseDownloadMiddleware):
try:
self.backend_factory = import_member(settings.DOWNLOADVIEW_BACKEND)
except AttributeError:
raise ImproperlyConfigured('SmartDownloadMiddleware requires '
'settings.DOWNLOADVIEW_BACKEND')
raise ImproperlyConfigured(
"SmartDownloadMiddleware requires " "settings.DOWNLOADVIEW_BACKEND"
)
def auto_configure_backend_options(self):
"""Populate :attr:`dispatcher` using :attr:`factory` and
@ -146,8 +154,9 @@ class SmartDownloadMiddleware(BaseDownloadMiddleware):
try:
options_list = copy.deepcopy(settings.DOWNLOADVIEW_RULES)
except AttributeError:
raise ImproperlyConfigured('SmartDownloadMiddleware requires '
'settings.DOWNLOADVIEW_RULES')
raise ImproperlyConfigured(
"SmartDownloadMiddleware requires " "settings.DOWNLOADVIEW_RULES"
)
for key, options in enumerate(options_list):
args = []
kwargs = {}
@ -155,9 +164,9 @@ class SmartDownloadMiddleware(BaseDownloadMiddleware):
kwargs = options
else:
args = options
if 'backend' in kwargs: # Specific backend for this rule.
factory = import_member(kwargs['backend'])
del kwargs['backend']
if "backend" in kwargs: # Specific backend for this rule.
factory = import_member(kwargs["backend"])
del kwargs["backend"]
else: # Fallback to global backend.
factory = self.backend_factory
middleware_instance = factory(*args, **kwargs)
@ -174,11 +183,10 @@ class NoRedirectionMatch(Exception):
class ProxiedDownloadMiddleware(RealDownloadMiddleware):
"""Base class for middlewares that use optimizations of reverse proxies."""
def __init__(self,
get_response=None,
source_dir=None,
source_url=None,
destination_url=None):
def __init__(
self, get_response=None, source_dir=None, source_url=None, destination_url=None
):
"""Constructor."""
super(ProxiedDownloadMiddleware, self).__init__(get_response)
@ -189,7 +197,7 @@ class ProxiedDownloadMiddleware(RealDownloadMiddleware):
def get_redirect_url(self, response):
"""Return redirect URL for file wrapped into response."""
url = None
file_url = ''
file_url = ""
if self.source_url:
try:
file_url = response.file.url
@ -197,9 +205,9 @@ class ProxiedDownloadMiddleware(RealDownloadMiddleware):
pass
else:
if file_url.startswith(self.source_url):
file_url = file_url[len(self.source_url):]
file_url = file_url[len(self.source_url) :]
url = file_url
file_name = ''
file_name = ""
if url is None and self.source_dir:
try:
file_name = response.file.name
@ -208,17 +216,21 @@ class ProxiedDownloadMiddleware(RealDownloadMiddleware):
else:
if file_name.startswith(self.source_dir):
file_name = os.path.relpath(file_name, self.source_dir)
url = file_name.replace(os.path.sep, '/')
url = file_name.replace(os.path.sep, "/")
if url is None:
message = ("""Couldn't capture/convert file attributes into a """
"""redirection. """
"""``source_url`` is "%(source_url)s", """
"""file's URL is "%(file_url)s". """
"""``source_dir`` is "%(source_dir)s", """
"""file's name is "%(file_name)s". """
% {'source_url': self.source_url,
'file_url': file_url,
'source_dir': self.source_dir,
'file_name': file_name})
message = (
"""Couldn't capture/convert file attributes into a """
"""redirection. """
"""``source_url`` is "%(source_url)s", """
"""file's URL is "%(file_url)s". """
"""``source_dir`` is "%(source_dir)s", """
"""file's name is "%(file_name)s". """
% {
"source_url": self.source_url,
"file_url": file_url,
"source_dir": self.source_dir,
"file_name": file_name,
}
)
raise NoRedirectionMatch(message)
return '/'.join((self.destination_url.rstrip('/'), url.lstrip('/')))
return "/".join((self.destination_url.rstrip("/"), url.lstrip("/")))

View file

@ -7,7 +7,6 @@ See also `Nginx X-accel documentation <http://wiki.nginx.org/X-accel>`_ and
"""
# API shortcuts.
from django_downloadview.nginx.decorators import x_accel_redirect # NoQA
from django_downloadview.nginx.middlewares import XAccelRedirectMiddleware # NoQA
from django_downloadview.nginx.response import XAccelRedirectResponse # NoQA
from django_downloadview.nginx.tests import assert_x_accel_redirect # NoQA
from django_downloadview.nginx.middlewares import ( # NoQA
XAccelRedirectMiddleware)

View file

@ -3,8 +3,10 @@ import warnings
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django_downloadview.middlewares import (ProxiedDownloadMiddleware,
NoRedirectionMatch)
from django_downloadview.middlewares import (
NoRedirectionMatch,
ProxiedDownloadMiddleware,
)
from django_downloadview.nginx.response import XAccelRedirectResponse
@ -17,17 +19,26 @@ class XAccelRedirectMiddleware(ProxiedDownloadMiddleware):
:py:class:`django_downloadview.decorators.DownloadDecorator`.
"""
def __init__(self,
get_response=None,
source_dir=None, source_url=None, destination_url=None,
expires=None, with_buffering=None, limit_rate=None,
media_root=None, media_url=None):
def __init__(
self,
get_response=None,
source_dir=None,
source_url=None,
destination_url=None,
expires=None,
with_buffering=None,
limit_rate=None,
media_root=None,
media_url=None,
):
"""Constructor."""
if media_url is not None:
warnings.warn("%s ``media_url`` is deprecated. Use "
"``destination_url`` instead."
% self.__class__.__name__,
DeprecationWarning)
warnings.warn(
"%s ``media_url`` is deprecated. Use "
"``destination_url`` instead." % self.__class__.__name__,
DeprecationWarning,
)
if destination_url is None:
destination_url = media_url
else:
@ -35,9 +46,11 @@ class XAccelRedirectMiddleware(ProxiedDownloadMiddleware):
else:
destination_url = destination_url
if media_root is not None:
warnings.warn("%s ``media_root`` is deprecated. Use "
"``source_dir`` instead." % self.__class__.__name__,
DeprecationWarning)
warnings.warn(
"%s ``media_root`` is deprecated. Use "
"``source_dir`` instead." % self.__class__.__name__,
DeprecationWarning,
)
if source_dir is None:
source_dir = media_root
else:
@ -46,7 +59,8 @@ class XAccelRedirectMiddleware(ProxiedDownloadMiddleware):
source_dir = source_dir
super(XAccelRedirectMiddleware, self).__init__(
get_response, source_dir, source_url, destination_url)
get_response, source_dir, source_url, destination_url
)
self.expires = expires
self.with_buffering = with_buffering
@ -65,13 +79,15 @@ class XAccelRedirectMiddleware(ProxiedDownloadMiddleware):
expires = response.expires
except AttributeError:
expires = None
return XAccelRedirectResponse(redirect_url=redirect_url,
content_type=response['Content-Type'],
basename=response.basename,
expires=expires,
with_buffering=self.with_buffering,
limit_rate=self.limit_rate,
attachment=response.attachment)
return XAccelRedirectResponse(
redirect_url=redirect_url,
content_type=response["Content-Type"],
basename=response.basename,
expires=expires,
with_buffering=self.with_buffering,
limit_rate=self.limit_rate,
attachment=response.attachment,
)
class SingleXAccelRedirectMiddleware(XAccelRedirectMiddleware):
@ -108,12 +124,14 @@ class SingleXAccelRedirectMiddleware(XAccelRedirectMiddleware):
Replaced by ``NGINX_DOWNLOAD_MIDDLEWARE_DESTINATION_URL``.
"""
def __init__(self, get_response=None):
"""Use Django settings as configuration."""
if settings.NGINX_DOWNLOAD_MIDDLEWARE_DESTINATION_URL is None:
raise ImproperlyConfigured(
'settings.NGINX_DOWNLOAD_MIDDLEWARE_DESTINATION_URL is '
'required by %s middleware' % self.__class__.__name__)
"settings.NGINX_DOWNLOAD_MIDDLEWARE_DESTINATION_URL is "
"required by %s middleware" % self.__class__.__name__
)
super(SingleXAccelRedirectMiddleware, self).__init__(
get_response=get_response,
source_dir=settings.NGINX_DOWNLOAD_MIDDLEWARE_SOURCE_DIR,
@ -121,4 +139,5 @@ class SingleXAccelRedirectMiddleware(XAccelRedirectMiddleware):
destination_url=settings.NGINX_DOWNLOAD_MIDDLEWARE_DESTINATION_URL,
expires=settings.NGINX_DOWNLOAD_MIDDLEWARE_EXPIRES,
with_buffering=settings.NGINX_DOWNLOAD_MIDDLEWARE_WITH_BUFFERING,
limit_rate=settings.NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE)
limit_rate=settings.NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE,
)

View file

@ -3,30 +3,36 @@ from datetime import timedelta
from django.utils.timezone import now
from django_downloadview.response import (ProxiedDownloadResponse,
content_disposition)
from django_downloadview.response import ProxiedDownloadResponse, content_disposition
from django_downloadview.utils import content_type_to_charset, url_basename
class XAccelRedirectResponse(ProxiedDownloadResponse):
"Http response that delegates serving file to Nginx via X-Accel headers."
def __init__(self, redirect_url, content_type, basename=None, expires=None,
with_buffering=None, limit_rate=None, attachment=True):
def __init__(
self,
redirect_url,
content_type,
basename=None,
expires=None,
with_buffering=None,
limit_rate=None,
attachment=True,
):
"""Return a HttpResponse with headers for Nginx X-Accel-Redirect."""
super(XAccelRedirectResponse, self).__init__(content_type=content_type)
if attachment:
self.basename = basename or url_basename(redirect_url,
content_type)
self['Content-Disposition'] = content_disposition(self.basename)
self['X-Accel-Redirect'] = redirect_url
self['X-Accel-Charset'] = content_type_to_charset(content_type)
self.basename = basename or url_basename(redirect_url, content_type)
self["Content-Disposition"] = content_disposition(self.basename)
self["X-Accel-Redirect"] = redirect_url
self["X-Accel-Charset"] = content_type_to_charset(content_type)
if with_buffering is not None:
self['X-Accel-Buffering'] = with_buffering and 'yes' or 'no'
self["X-Accel-Buffering"] = with_buffering and "yes" or "no"
if expires:
expire_seconds = timedelta(expires - now()).seconds
self['X-Accel-Expires'] = expire_seconds
self["X-Accel-Expires"] = expire_seconds
elif expires is not None: # We explicitely want it off.
self['X-Accel-Expires'] = 'off'
self["X-Accel-Expires"] = "off"
if limit_rate is not None:
self['X-Accel-Limit-Rate'] = \
limit_rate and '%d' % limit_rate or 'off'
self["X-Accel-Limit-Rate"] = limit_rate and "%d" % limit_rate or "off"

View file

@ -12,10 +12,9 @@ import warnings
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
# In version 1.3, former XAccelRedirectMiddleware has been renamed to
# SingleXAccelRedirectMiddleware. So tell the users.
deprecated_middleware = 'django_downloadview.nginx.XAccelRedirectMiddleware'
deprecated_middleware = "django_downloadview.nginx.XAccelRedirectMiddleware"
def get_middlewares():
@ -27,15 +26,18 @@ def get_middlewares():
if deprecated_middleware in get_middlewares():
raise ImproperlyConfigured(
'{deprecated_middleware} middleware has been renamed as of '
'django-downloadview version 1.3. You may use '
"{deprecated_middleware} middleware has been renamed as of "
"django-downloadview version 1.3. You may use "
'"django_downloadview.nginx.SingleXAccelRedirectMiddleware" instead, '
'or upgrade to "django_downloadview.SmartDownloadDispatcher". ')
'or upgrade to "django_downloadview.SmartDownloadDispatcher". '
)
deprecated_msg = 'settings.{deprecated} is deprecated. You should combine ' \
'"django_downloadview.SmartDownloadDispatcher" with ' \
'with DOWNLOADVIEW_BACKEND and DOWNLOADVIEW_RULES instead.'
deprecated_msg = (
"settings.{deprecated} is deprecated. You should combine "
'"django_downloadview.SmartDownloadDispatcher" with '
"with DOWNLOADVIEW_BACKEND and DOWNLOADVIEW_RULES instead."
)
#: Default value for X-Accel-Buffering header.
@ -50,10 +52,9 @@ deprecated_msg = 'settings.{deprecated} is deprecated. You should combine ' \
#: If set to ``False``, Nginx buffering is disabled.
#: If set to ``True``, Nginx buffering is enabled.
DEFAULT_WITH_BUFFERING = None
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_WITH_BUFFERING'
setting_name = "NGINX_DOWNLOAD_MIDDLEWARE_WITH_BUFFERING"
if hasattr(settings, setting_name):
warnings.warn(deprecated_msg.format(deprecated=setting_name),
DeprecationWarning)
warnings.warn(deprecated_msg.format(deprecated=setting_name), DeprecationWarning)
if not hasattr(settings, setting_name):
setattr(settings, setting_name, DEFAULT_WITH_BUFFERING)
@ -69,10 +70,9 @@ if not hasattr(settings, setting_name):
#: If set to ``False``, Nginx limit rate is disabled.
#: Else, it indicates the limit rate in bytes.
DEFAULT_LIMIT_RATE = None
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE'
setting_name = "NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE"
if hasattr(settings, setting_name):
warnings.warn(deprecated_msg.format(deprecated=setting_name),
DeprecationWarning)
warnings.warn(deprecated_msg.format(deprecated=setting_name), DeprecationWarning)
if not hasattr(settings, setting_name):
setattr(settings, setting_name, DEFAULT_LIMIT_RATE)
@ -88,49 +88,43 @@ if not hasattr(settings, setting_name):
#: If set to ``False``, Nginx buffering is disabled.
#: Else, it indicates the expiration delay, in seconds.
DEFAULT_EXPIRES = None
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_EXPIRES'
setting_name = "NGINX_DOWNLOAD_MIDDLEWARE_EXPIRES"
if hasattr(settings, setting_name):
warnings.warn(deprecated_msg.format(deprecated=setting_name),
DeprecationWarning)
warnings.warn(deprecated_msg.format(deprecated=setting_name), DeprecationWarning)
if not hasattr(settings, setting_name):
setattr(settings, setting_name, DEFAULT_EXPIRES)
#: Default value for settings.NGINX_DOWNLOAD_MIDDLEWARE_SOURCE_DIR.
DEFAULT_SOURCE_DIR = settings.MEDIA_ROOT
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT'
setting_name = "NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT"
if hasattr(settings, setting_name):
warnings.warn(deprecated_msg.format(deprecated=setting_name),
DeprecationWarning)
warnings.warn(deprecated_msg.format(deprecated=setting_name), DeprecationWarning)
DEFAULT_SOURCE_DIR = settings.NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_SOURCE_DIR'
setting_name = "NGINX_DOWNLOAD_MIDDLEWARE_SOURCE_DIR"
if hasattr(settings, setting_name):
warnings.warn(deprecated_msg.format(deprecated=setting_name),
DeprecationWarning)
warnings.warn(deprecated_msg.format(deprecated=setting_name), DeprecationWarning)
if not hasattr(settings, setting_name):
setattr(settings, setting_name, DEFAULT_SOURCE_DIR)
#: Default value for settings.NGINX_DOWNLOAD_MIDDLEWARE_SOURCE_URL.
DEFAULT_SOURCE_URL = settings.MEDIA_URL
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_SOURCE_URL'
setting_name = "NGINX_DOWNLOAD_MIDDLEWARE_SOURCE_URL"
if hasattr(settings, setting_name):
warnings.warn(deprecated_msg.format(deprecated=setting_name),
DeprecationWarning)
warnings.warn(deprecated_msg.format(deprecated=setting_name), DeprecationWarning)
if not hasattr(settings, setting_name):
setattr(settings, setting_name, DEFAULT_SOURCE_URL)
#: Default value for settings.NGINX_DOWNLOAD_MIDDLEWARE_DESTINATION_URL.
DEFAULT_DESTINATION_URL = None
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL'
setting_name = "NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL"
if hasattr(settings, setting_name):
warnings.warn(deprecated_msg.format(deprecated=setting_name),
DeprecationWarning)
warnings.warn(deprecated_msg.format(deprecated=setting_name), DeprecationWarning)
DEFAULT_SOURCE_DIR = settings.NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL
setting_name = 'NGINX_DOWNLOAD_MIDDLEWARE_DESTINATION_URL'
setting_name = "NGINX_DOWNLOAD_MIDDLEWARE_DESTINATION_URL"
if hasattr(settings, setting_name):
warnings.warn(deprecated_msg.format(deprecated=setting_name),
DeprecationWarning)
warnings.warn(deprecated_msg.format(deprecated=setting_name), DeprecationWarning)
if not hasattr(settings, setting_name):
setattr(settings, setting_name, DEFAULT_DESTINATION_URL)

View file

@ -7,6 +7,7 @@ class XAccelRedirectValidator(object):
See also :py:func:`assert_x_accel_redirect` shortcut function.
"""
def __call__(self, test_case, response, **assertions):
"""Assert that ``response`` is a valid X-Accel-Redirect response.
@ -36,7 +37,7 @@ class XAccelRedirectValidator(object):
"""
self.assert_x_accel_redirect_response(test_case, response)
for key, value in assertions.items():
assert_func = getattr(self, 'assert_%s' % key)
assert_func = getattr(self, "assert_%s" % key)
assert_func(test_case, response, value)
def assert_x_accel_redirect_response(self, test_case, response):
@ -46,45 +47,45 @@ class XAccelRedirectValidator(object):
test_case.assertEqual(response.basename, value)
def assert_content_type(self, test_case, response, value):
test_case.assertEqual(response['Content-Type'], value)
test_case.assertEqual(response["Content-Type"], value)
def assert_redirect_url(self, test_case, response, value):
test_case.assertEqual(response['X-Accel-Redirect'], value)
test_case.assertEqual(response["X-Accel-Redirect"], value)
def assert_charset(self, test_case, response, value):
test_case.assertEqual(response['X-Accel-Charset'], value)
test_case.assertEqual(response["X-Accel-Charset"], value)
def assert_with_buffering(self, test_case, response, value):
header = 'X-Accel-Buffering'
header = "X-Accel-Buffering"
if value is None:
test_case.assertFalse(header in response)
elif value:
test_case.assertEqual(header, 'yes')
test_case.assertEqual(header, "yes")
else:
test_case.assertEqual(header, 'no')
test_case.assertEqual(header, "no")
def assert_expires(self, test_case, response, value):
header = 'X-Accel-Expires'
header = "X-Accel-Expires"
if value is None:
test_case.assertFalse(header in response)
elif not value:
test_case.assertEqual(header, 'off')
test_case.assertEqual(header, "off")
else:
test_case.assertEqual(header, value)
def assert_limit_rate(self, test_case, response, value):
header = 'X-Accel-Limit-Rate'
header = "X-Accel-Limit-Rate"
if value is None:
test_case.assertFalse(header in response)
elif not value:
test_case.assertEqual(header, 'off')
test_case.assertEqual(header, "off")
else:
test_case.assertEqual(header, value)
def assert_attachment(self, test_case, response, value):
header = 'Content-Disposition'
header = "Content-Disposition"
if value:
test_case.assertTrue(response[header].startswith('attachment'))
test_case.assertTrue(response[header].startswith("attachment"))
else:
test_case.assertFalse(header in response)

View file

@ -1,6 +1,6 @@
""":py:class:`django.http.HttpResponse` subclasses."""
import os
import mimetypes
import os
import re
import unicodedata
from urllib.parse import quote
@ -11,7 +11,7 @@ from django.utils.encoding import force_str
def encode_basename_ascii(value):
u"""Return US-ASCII encoded ``value`` for Content-Disposition header.
"""Return US-ASCII encoded ``value`` for Content-Disposition header.
>>> print(encode_basename_ascii(u'éà'))
ea
@ -30,17 +30,17 @@ def encode_basename_ascii(value):
"""
if isinstance(value, bytes):
value = value.decode('utf-8')
value = value.decode("utf-8")
ascii_basename = str(value)
ascii_basename = unicodedata.normalize('NFKD', ascii_basename)
ascii_basename = ascii_basename.encode('ascii', 'ignore')
ascii_basename = ascii_basename.decode('ascii')
ascii_basename = re.sub(r'[\s]', '_', ascii_basename)
ascii_basename = unicodedata.normalize("NFKD", ascii_basename)
ascii_basename = ascii_basename.encode("ascii", "ignore")
ascii_basename = ascii_basename.decode("ascii")
ascii_basename = re.sub(r"[\s]", "_", ascii_basename)
return ascii_basename
def encode_basename_utf8(value):
u"""Return UTF-8 encoded ``value`` for use in Content-Disposition header.
"""Return UTF-8 encoded ``value`` for use in Content-Disposition header.
>>> print(encode_basename_utf8(u' .txt'))
%20.txt
@ -53,7 +53,7 @@ def encode_basename_utf8(value):
def content_disposition(filename):
u"""Return value of ``Content-Disposition`` header with 'attachment'.
"""Return value of ``Content-Disposition`` header with 'attachment'.
>>> print(content_disposition('demo.txt'))
attachment; filename="demo.txt"
@ -71,15 +71,16 @@ def content_disposition(filename):
"""
if not filename:
return 'attachment'
return "attachment"
ascii_filename = encode_basename_ascii(filename)
utf8_filename = encode_basename_utf8(filename)
if ascii_filename == utf8_filename: # ASCII only.
return "attachment; filename=\"{ascii}\"".format(ascii=ascii_filename)
return f'attachment; filename="{ascii_filename}"'
else:
return "attachment; filename=\"{ascii}\"; filename*=UTF-8''{utf8}" \
.format(ascii=ascii_filename,
utf8=utf8_filename)
return (
f'attachment; filename="{ascii_filename}"; '
f"filename*=UTF-8''{utf8_filename}"
)
class DownloadResponse(StreamingHttpResponse):
@ -115,9 +116,17 @@ class DownloadResponse(StreamingHttpResponse):
attributes (size, name, ...).
"""
def __init__(self, file_instance, attachment=True, basename=None,
status=200, content_type=None, file_mimetype=None,
file_encoding=None):
def __init__(
self,
file_instance,
attachment=True,
basename=None,
status=200,
content_type=None,
file_mimetype=None,
file_encoding=None,
):
"""Constructor.
:param content_type: Value for ``Content-Type`` header.
@ -129,9 +138,9 @@ class DownloadResponse(StreamingHttpResponse):
#: A :doc:`file wrapper instance </files>`, such as
#: :class:`~django.core.files.base.File`.
self.file = file_instance
super(DownloadResponse, self).__init__(streaming_content=self.file,
status=status,
content_type=content_type)
super(DownloadResponse, self).__init__(
streaming_content=self.file, status=status, content_type=content_type
)
#: Client-side name of the file to stream.
#: Only used if ``attachment`` is ``True``.
@ -142,7 +151,7 @@ class DownloadResponse(StreamingHttpResponse):
#: Affects ``Content-Disposition`` header.
self.attachment = attachment
if not content_type:
del self['Content-Type'] # Will be set later.
del self["Content-Type"] # Will be set later.
#: Value for file's mimetype.
#: If ``None`` (the default), then the file's mimetype will be guessed
@ -175,14 +184,14 @@ class DownloadResponse(StreamingHttpResponse):
return self._default_headers
except AttributeError:
headers = {}
headers['Content-Type'] = self.get_content_type()
headers["Content-Type"] = self.get_content_type()
try:
headers['Content-Length'] = self.file.size
headers["Content-Length"] = self.file.size
except (AttributeError, NotImplementedError):
pass # Generated files.
if self.attachment:
basename = self.get_basename()
headers['Content-Disposition'] = content_disposition(basename)
headers["Content-Disposition"] = content_disposition(basename)
self._default_headers = headers
return self._default_headers
@ -208,15 +217,13 @@ class DownloadResponse(StreamingHttpResponse):
try:
return self.file.content_type
except AttributeError:
content_type_template = '{mime_type}; charset={charset}'
return content_type_template.format(mime_type=self.get_mime_type(),
charset=self.get_charset())
return f"{self.get_mime_type()}; charset={self.get_charset()}"
def get_mime_type(self):
"""Return mime-type of the file."""
if self.file_mimetype is not None:
return self.file_mimetype
default_mime_type = 'application/octet-stream'
default_mime_type = "application/octet-stream"
basename = self.get_basename()
mime_type, encoding = mimetypes.guess_type(basename)
return mime_type or default_mime_type

View file

@ -2,17 +2,25 @@
from django_downloadview.views.path import PathDownloadView
def sendfile(request, filename, attachment=False, attachment_filename=None,
mimetype=None, encoding=None):
def sendfile(
request,
filename,
attachment=False,
attachment_filename=None,
mimetype=None,
encoding=None,
):
"""Port of django-sendfile's API in django-downloadview.
Instantiates a :class:`~django_downloadview.views.path.PathDownloadView` to
stream the file by ``filename``.
"""
view = PathDownloadView.as_view(path=filename,
attachment=attachment,
basename=attachment_filename,
mimetype=mimetype,
encoding=encoding)
view = PathDownloadView.as_view(
path=filename,
attachment=attachment,
basename=attachment_filename,
mimetype=mimetype,
encoding=encoding,
)
return view(request)

View file

@ -6,8 +6,7 @@ from django.test.utils import override_settings
from django.utils.encoding import force_bytes
from django_downloadview.middlewares import is_download_response
from django_downloadview.response import (encode_basename_ascii,
encode_basename_utf8)
from django_downloadview.response import encode_basename_ascii, encode_basename_utf8
def setup_view(view, request, *args, **kwargs):
@ -66,11 +65,12 @@ class temporary_media_root(override_settings):
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
self.options["MEDIA_ROOT"] = tmp_dir
super(temporary_media_root, self).enable()
def disable(self):
@ -82,6 +82,7 @@ class temporary_media_root(override_settings):
class DownloadResponseValidator(object):
"""Utility class to validate DownloadResponse instances."""
def __call__(self, test_case, response, **assertions):
"""Assert that ``response`` is a valid DownloadResponse instance.
@ -102,7 +103,7 @@ class DownloadResponseValidator(object):
"""
self.assert_download_response(test_case, response)
for key, value in assertions.items():
assert_func = getattr(self, 'assert_%s' % key)
assert_func = getattr(self, "assert_%s" % key)
assert_func(test_case, response, value)
def assert_download_response(self, test_case, response):
@ -116,40 +117,40 @@ class DownloadResponseValidator(object):
check_ascii = False
if ascii_name == utf8_name: # Only ASCII characters.
check_ascii = True
if "filename*=" in response['Content-Disposition']:
if "filename*=" in response["Content-Disposition"]:
check_utf8 = True
else:
check_utf8 = True
if "filename=" in response['Content-Disposition']:
if "filename=" in response["Content-Disposition"]:
check_ascii = True
if check_ascii:
test_case.assertIn('filename="{name}"'.format(
name=ascii_name),
response['Content-Disposition'])
test_case.assertIn(
f'filename="{ascii_name}"', response["Content-Disposition"],
)
if check_utf8:
test_case.assertIn(
"filename*=UTF-8''{name}".format(name=utf8_name),
response['Content-Disposition'])
f"filename*=UTF-8''{utf8_name}", response["Content-Disposition"],
)
def assert_content_type(self, test_case, response, value):
test_case.assertEqual(response['Content-Type'], value)
test_case.assertEqual(response["Content-Type"], value)
def assert_mime_type(self, test_case, response, value):
test_case.assertTrue(response['Content-Type'].startswith(value))
test_case.assertTrue(response["Content-Type"].startswith(value))
def assert_content(self, test_case, response, value):
"""Assert value equals response's content (byte comparison)."""
parts = [force_bytes(s) for s in response.streaming_content]
test_case.assertEqual(b''.join(parts), force_bytes(value))
test_case.assertEqual(b"".join(parts), force_bytes(value))
def assert_attachment(self, test_case, response, value):
if value:
test_case.assertTrue(
'attachment;' in response['Content-Disposition'])
test_case.assertTrue("attachment;" in response["Content-Disposition"])
else:
test_case.assertTrue(
'Content-Disposition' not in response or
'attachment;' not in response['Content-Disposition'])
"Content-Disposition" not in response
or "attachment;" not in response["Content-Disposition"]
)
def assert_download_response(test_case, response, **assertions):

View file

@ -1,8 +1,7 @@
"""Utility functions that may be implemented in external packages."""
import re
charset_pattern = re.compile(r'charset=(?P<charset>.+)$', re.I | re.U)
charset_pattern = re.compile(r"charset=(?P<charset>.+)$", re.I | re.U)
def content_type_to_charset(content_type):
@ -15,7 +14,7 @@ def content_type_to_charset(content_type):
"""
match = re.search(charset_pattern, content_type)
if match:
return match.group('charset')
return match.group("charset")
def url_basename(url, content_type):
@ -29,7 +28,7 @@ def url_basename(url, content_type):
somefile.rst
"""
return url.split('/')[-1]
return url.split("/")[-1]
def import_member(import_string):
@ -41,6 +40,6 @@ def import_member(import_string):
True
"""
module_name, factory_name = str(import_string).rsplit('.', 1)
module_name, factory_name = str(import_string).rsplit(".", 1)
module = __import__(module_name, globals(), locals(), [factory_name], 0)
return getattr(module, factory_name)

View file

@ -1,9 +1,8 @@
"""Views to stream files."""
# API shortcuts.
from django_downloadview.views.base import (DownloadMixin, # NoQA
BaseDownloadView)
from django_downloadview.views.base import BaseDownloadView, DownloadMixin # NoQA
from django_downloadview.views.http import HTTPDownloadView # NoQA
from django_downloadview.views.object import ObjectDownloadView # NoQA
from django_downloadview.views.path import PathDownloadView # NoQA
from django_downloadview.views.storage import StorageDownloadView # NoQA
from django_downloadview.views.object import ObjectDownloadView # NoQA
from django_downloadview.views.http import HTTPDownloadView # NoQA
from django_downloadview.views.virtual import VirtualDownloadView # NoQA

View file

@ -2,7 +2,7 @@
:class:`BaseDownloadView`"""
import calendar
from django.http import HttpResponseNotModified, Http404
from django.http import Http404, HttpResponseNotModified
from django.views.generic.base import View
from django.views.static import was_modified_since
@ -25,6 +25,7 @@ class DownloadMixin(object):
returned by :py:meth:`get_file`.
"""
#: Response class, to be used in :py:meth:`render_to_response`.
response_class = DownloadResponse
@ -113,7 +114,8 @@ class DownloadMixin(object):
except (AttributeError, NotImplementedError):
try:
modification_time = calendar.timegm(
file_instance.modified_time.utctimetuple())
file_instance.modified_time.utctimetuple()
)
size = file_instance.size
except (AttributeError, NotImplementedError) as e:
print("!=======!", e)
@ -127,11 +129,11 @@ class DownloadMixin(object):
def download_response(self, *response_args, **response_kwargs):
"""Return :class:`~django_downloadview.response.DownloadResponse`."""
response_kwargs.setdefault('file_instance', self.file_instance)
response_kwargs.setdefault('attachment', self.attachment)
response_kwargs.setdefault('basename', self.get_basename())
response_kwargs.setdefault('file_mimetype', self.get_mimetype())
response_kwargs.setdefault('file_encoding', self.get_encoding())
response_kwargs.setdefault("file_instance", self.file_instance)
response_kwargs.setdefault("attachment", self.attachment)
response_kwargs.setdefault("basename", self.get_basename())
response_kwargs.setdefault("file_mimetype", self.get_mimetype())
response_kwargs.setdefault("file_encoding", self.get_encoding())
response = self.response_class(*response_args, **response_kwargs)
return response
@ -155,7 +157,7 @@ class DownloadMixin(object):
except exceptions.FileNotFound:
return self.file_not_found_response()
# Respect the If-Modified-Since header.
since = self.request.META.get('HTTP_IF_MODIFIED_SINCE', None)
since = self.request.META.get("HTTP_IF_MODIFIED_SINCE", None)
if since is not None:
if not self.was_modified_since(self.file_instance, since):
return self.not_modified_response(**response_kwargs)
@ -165,6 +167,7 @@ class DownloadMixin(object):
class BaseDownloadView(DownloadMixin, View):
"""A base :class:`DownloadMixin` that implements :meth:`get`."""
def get(self, request, *args, **kwargs):
"""Handle GET requests: stream a file."""
return self.render_to_response()

View file

@ -1,14 +1,15 @@
"""Stream files given an URL, i.e. files you want to proxy."""
import requests
from django_downloadview.files import HTTPFile
from django_downloadview.views.base import BaseDownloadView
import requests
class HTTPDownloadView(BaseDownloadView):
"""Proxy files that live on remote servers."""
#: URL to download (the one we are proxying).
url = u''
url = ""
#: Additional keyword arguments for request handler.
request_kwargs = {}
@ -39,7 +40,9 @@ class HTTPDownloadView(BaseDownloadView):
def get_file(self):
"""Return wrapper which has an ``url`` attribute."""
return HTTPFile(request_factory=self.get_request_factory(),
name=self.get_basename(),
url=self.get_url(),
**self.get_request_kwargs())
return HTTPFile(
request_factory=self.get_request_factory(),
name=self.get_basename(),
url=self.get_url(),
**self.get_request_kwargs()
)

View file

@ -30,9 +30,10 @@ class ObjectDownloadView(SingleObjectMixin, BaseDownloadView):
local filesystem.
"""
#: Name of the model's attribute which contains the file to be streamed.
#: Typically the name of a FileField.
file_field = 'file'
file_field = "file"
#: Optional name of the model's attribute which contains the basename.
basename_field = None
@ -70,13 +71,11 @@ class ObjectDownloadView(SingleObjectMixin, BaseDownloadView):
"""
file_instance = getattr(self.object, self.file_field)
if not file_instance:
raise FileNotFound('Field="{field}" on object="{object}" is '
'empty'.format(
field=self.file_field,
object=self.object))
for field in ('encoding', 'mime_type', 'charset', 'modification_time',
'size'):
model_field = getattr(self, '%s_field' % field, False)
raise FileNotFound(
f'Field="{self.file_field}" on object="{self.object}" is empty'
)
for field in ("encoding", "mime_type", "charset", "modification_time", "size"):
model_field = getattr(self, "%s_field" % field, False)
if model_field:
value = getattr(self.object, model_field)
setattr(file_instance, field, value)
@ -86,8 +85,8 @@ class ObjectDownloadView(SingleObjectMixin, BaseDownloadView):
"""Return client-side filename."""
basename = super(ObjectDownloadView, self).get_basename()
if basename is None:
field = 'basename'
model_field = getattr(self, '%s_field' % field, False)
field = "basename"
model_field = getattr(self, "%s_field" % field, False)
if model_field:
basename = getattr(self.object, model_field)
return basename

View file

@ -9,6 +9,7 @@ from django_downloadview.views.base import BaseDownloadView
class PathDownloadView(BaseDownloadView):
"""Serve a file using filename."""
#: Server-side name (including path) of the file to serve.
#:
#: Filename is supposed to be an absolute filename of a file located on the
@ -16,7 +17,7 @@ class PathDownloadView(BaseDownloadView):
path = None
#: Name of the URL argument that contains path.
path_url_kwarg = 'path'
path_url_kwarg = "path"
def get_path(self):
"""Return actual path of the file to serve.
@ -34,5 +35,5 @@ class PathDownloadView(BaseDownloadView):
"""Use path to return wrapper around file to serve."""
filename = self.get_path()
if not os.path.isfile(filename):
raise FileNotFound('File "{0}" does not exists'.format(filename))
return File(open(filename, 'rb'))
raise FileNotFound(f'File "{filename}" does not exists')
return File(open(filename, "rb"))

View file

@ -7,6 +7,7 @@ from django_downloadview.views.path import PathDownloadView
class StorageDownloadView(PathDownloadView):
"""Serve a file using storage and filename."""
#: Storage the file to serve belongs to.
storage = DefaultStorage()

View file

@ -8,6 +8,7 @@ class VirtualDownloadView(BaseDownloadView):
Override the :py:meth:`get_file` method to customize file wrapper.
"""
def was_modified_since(self, file_instance, since):
"""Delegate to file wrapper's was_modified_since, or return True.

View file

@ -55,7 +55,7 @@ See details in :attr:`attachment API documentation
.. literalinclude:: /../demo/demoproject/object/views.py
:language: python
:lines: 1-5, 20-23
:lines: 1-2, 19
************************************

View file

@ -38,7 +38,7 @@ object. Serve it with Django's builtin
.. literalinclude:: /../demo/demoproject/virtual/views.py
:language: python
:lines: 3-5, 8-13
:lines: 1, 3, 7-11
**************
@ -50,7 +50,7 @@ download view via :class:`~django_downloadview.files.VirtualFile`:
.. literalinclude:: /../demo/demoproject/virtual/views.py
:language: python
:lines: 1-2, 5-6, 14-20
:lines: 1-4, 5-6, 13-17
************************
@ -63,7 +63,7 @@ Let's consider you have a generator function (``yield``) or an iterator object
.. literalinclude:: /../demo/demoproject/virtual/views.py
:language: python
:lines: 23-26
:lines: 20-23
Stream generated content using :class:`VirtualDownloadView`,
@ -72,7 +72,7 @@ Stream generated content using :class:`VirtualDownloadView`,
.. literalinclude:: /../demo/demoproject/virtual/views.py
:language: python
:lines: 5-9, 29-33
:lines: 3, 26-30
*************

View file

@ -10,6 +10,7 @@ from django.test.utils import override_settings
class APITestCase(unittest.TestCase):
"""Make sure django_downloadview exposes API."""
def assert_module_attributes(self, module_path, attribute_names):
"""Assert imported ``module_path`` has ``attribute_names``."""
module = import_module(module_path)
@ -18,8 +19,9 @@ class APITestCase(unittest.TestCase):
if not hasattr(module, attribute_name):
missing_attributes.append(attribute_name)
if missing_attributes:
self.fail('Missing attributes in "{module}": {attributes}'.format(
module=module_path, attributes=', '.join(missing_attributes)))
self.fail(
'Missing attributes in "{module_path}": {", ".join(missing_attributes)}'
)
def test_root_attributes(self):
"""API is exposed in django_downloadview root package.
@ -33,87 +35,93 @@ class APITestCase(unittest.TestCase):
"""
api = [
# Views:
'ObjectDownloadView',
'StorageDownloadView',
'PathDownloadView',
'HTTPDownloadView',
'VirtualDownloadView',
'BaseDownloadView',
'DownloadMixin',
"ObjectDownloadView",
"StorageDownloadView",
"PathDownloadView",
"HTTPDownloadView",
"VirtualDownloadView",
"BaseDownloadView",
"DownloadMixin",
# File wrappers:
'StorageFile',
'HTTPFile',
'VirtualFile',
"StorageFile",
"HTTPFile",
"VirtualFile",
# Responses:
'DownloadResponse',
'ProxiedDownloadResponse',
"DownloadResponse",
"ProxiedDownloadResponse",
# Middlewares:
'BaseDownloadMiddleware',
'DownloadDispatcherMiddleware',
'SmartDownloadMiddleware',
"BaseDownloadMiddleware",
"DownloadDispatcherMiddleware",
"SmartDownloadMiddleware",
# Testing:
'assert_download_response',
'setup_view',
'temporary_media_root',
"assert_download_response",
"setup_view",
"temporary_media_root",
# Utilities:
'StringIteratorIO',
'sendfile']
self.assert_module_attributes('django_downloadview', api)
"StringIteratorIO",
"sendfile",
]
self.assert_module_attributes("django_downloadview", api)
def test_nginx_attributes(self):
"""Nginx-related API is exposed in django_downloadview.nginx."""
api = [
'XAccelRedirectResponse',
'XAccelRedirectMiddleware',
'x_accel_redirect',
'assert_x_accel_redirect']
self.assert_module_attributes('django_downloadview.nginx', api)
"XAccelRedirectResponse",
"XAccelRedirectMiddleware",
"x_accel_redirect",
"assert_x_accel_redirect",
]
self.assert_module_attributes("django_downloadview.nginx", api)
def test_apache_attributes(self):
"""Apache-related API is exposed in django_downloadview.apache."""
api = [
'XSendfileResponse',
'XSendfileMiddleware',
'x_sendfile',
'assert_x_sendfile']
self.assert_module_attributes('django_downloadview.apache', api)
"XSendfileResponse",
"XSendfileMiddleware",
"x_sendfile",
"assert_x_sendfile",
]
self.assert_module_attributes("django_downloadview.apache", api)
def test_lighttpd_attributes(self):
"""Lighttpd-related API is exposed in django_downloadview.lighttpd."""
api = [
'XSendfileResponse',
'XSendfileMiddleware',
'x_sendfile',
'assert_x_sendfile']
self.assert_module_attributes('django_downloadview.lighttpd', api)
"XSendfileResponse",
"XSendfileMiddleware",
"x_sendfile",
"assert_x_sendfile",
]
self.assert_module_attributes("django_downloadview.lighttpd", api)
class DeprecatedAPITestCase(django.test.SimpleTestCase):
"""Make sure using deprecated items raise DeprecationWarning."""
def test_nginx_x_accel_redirect_middleware(self):
"XAccelRedirectMiddleware in settings triggers ImproperlyConfigured."
with override_settings(
MIDDLEWARE_CLASSES=[
'django_downloadview.nginx.XAccelRedirectMiddleware'],
MIDDLEWARE=[
'django_downloadview.nginx.XAccelRedirectMiddleware'],):
MIDDLEWARE_CLASSES=["django_downloadview.nginx.XAccelRedirectMiddleware"],
MIDDLEWARE=["django_downloadview.nginx.XAccelRedirectMiddleware"],
):
with self.assertRaises(ImproperlyConfigured):
import django_downloadview.nginx.settings
reload(django_downloadview.nginx.settings)
def test_nginx_x_accel_redirect_global_settings(self):
"""Global settings for Nginx middleware are deprecated."""
settings_overrides = {
'NGINX_DOWNLOAD_MIDDLEWARE_WITH_BUFFERING': True,
'NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE': 32,
'NGINX_DOWNLOAD_MIDDLEWARE_EXPIRES': 3600,
'NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT': '/',
'NGINX_DOWNLOAD_MIDDLEWARE_SOURCE_DIR': '/',
'NGINX_DOWNLOAD_MIDDLEWARE_SOURCE_URL': '/',
'NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL': '/',
'NGINX_DOWNLOAD_MIDDLEWARE_DESTINATION_URL': '/',
"NGINX_DOWNLOAD_MIDDLEWARE_WITH_BUFFERING": True,
"NGINX_DOWNLOAD_MIDDLEWARE_LIMIT_RATE": 32,
"NGINX_DOWNLOAD_MIDDLEWARE_EXPIRES": 3600,
"NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_ROOT": "/",
"NGINX_DOWNLOAD_MIDDLEWARE_SOURCE_DIR": "/",
"NGINX_DOWNLOAD_MIDDLEWARE_SOURCE_URL": "/",
"NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL": "/",
"NGINX_DOWNLOAD_MIDDLEWARE_DESTINATION_URL": "/",
}
import django_downloadview.nginx.settings
missed_warnings = []
for setting_name, setting_value in settings_overrides.items():
warnings.resetwarnings()
@ -124,7 +132,7 @@ class DeprecatedAPITestCase(django.test.SimpleTestCase):
caught = False
for warning_item in warning_list:
if warning_item.category == DeprecationWarning:
if 'deprecated' in str(warning_item.message):
if "deprecated" in str(warning_item.message):
if setting_name in str(warning_item.message):
caught = True
break
@ -132,5 +140,6 @@ class DeprecatedAPITestCase(django.test.SimpleTestCase):
missed_warnings.append(setting_name)
if missed_warnings:
self.fail(
'No DeprecationWarning raised about following settings: '
'{settings}.'.format(settings=', '.join(missed_warnings)))
f"No DeprecationWarning raised about following settings: "
f'{", ".join(missed_warnings)}.'
)

View file

@ -1,33 +1,33 @@
"""Tests around :mod:`django_downloadview.io`."""
import unittest
from django_downloadview import TextIteratorIO, BytesIteratorIO
from django_downloadview import BytesIteratorIO, TextIteratorIO
HELLO_TEXT = u'Hello world!\né\n'
HELLO_BYTES = b'Hello world!\n\xc3\xa9\n'
HELLO_TEXT = "Hello world!\né\n"
HELLO_BYTES = b"Hello world!\n\xc3\xa9\n"
def generate_hello_text():
"""Generate u'Hello world!\n'."""
yield u'Hello '
yield u'world!'
yield u'\n'
yield u'é'
yield u'\n'
yield "Hello "
yield "world!"
yield "\n"
yield "é"
yield "\n"
def generate_hello_bytes():
"""Generate b'Hello world!\n'."""
yield b'Hello '
yield b'world!'
yield b'\n'
yield b'\xc3\xa9'
yield b'\n'
yield b"Hello "
yield b"world!"
yield b"\n"
yield b"\xc3\xa9"
yield b"\n"
class TextIteratorIOTestCase(unittest.TestCase):
"""Tests around :class:`~django_downloadview.io.TextIteratorIO`."""
def test_read_text(self):
"""TextIteratorIO obviously accepts text generator."""
file_obj = TextIteratorIO(generate_hello_text())
@ -41,6 +41,7 @@ class TextIteratorIOTestCase(unittest.TestCase):
class BytesIteratorIOTestCase(unittest.TestCase):
"""Tests around :class:`~django_downloadview.io.BytesIteratorIO`."""
def test_read_bytes(self):
"""BytesIteratorIO obviously accepts bytes generator."""
file_obj = BytesIteratorIO(generate_hello_bytes())

View file

@ -2,17 +2,18 @@
import os
import unittest
tests_dir = os.path.dirname(os.path.abspath(__file__))
project_dir = os.path.dirname(tests_dir)
build_dir = os.path.join(project_dir, 'var', 'docs', 'html')
build_dir = os.path.join(project_dir, "var", "docs", "html")
class VersionTestCase(unittest.TestCase):
"""Various checks around project's version info."""
def get_version(self):
"""Return django_downloadview.__version__."""
from django_downloadview import __version__
return __version__
def test_version_present(self):
@ -20,32 +21,40 @@ class VersionTestCase(unittest.TestCase):
try:
self.get_version()
except ImportError:
self.fail('django_downloadview package has no __version__.')
self.fail("django_downloadview package has no __version__.")
def test_version_match(self):
"""django_downloadview.__version__ matches pkg_resources info."""
try:
import pkg_resources
except ImportError:
self.fail('Cannot import pkg_resources module. It is part of '
'setuptools, which is a dependency of '
'django_downloadview.')
distribution = pkg_resources.get_distribution('django-downloadview')
self.fail(
"Cannot import pkg_resources module. It is part of "
"setuptools, which is a dependency of "
"django_downloadview."
)
distribution = pkg_resources.get_distribution("django-downloadview")
installed_version = distribution.version
self.assertEqual(installed_version, self.get_version(),
'Version mismatch: django_downloadview.__version__ '
'is "%s" whereas pkg_resources tells "%s". '
'You may need to run ``make develop`` to update the '
'installed version in development environment.'
% (self.get_version(), installed_version))
self.assertEqual(
installed_version,
self.get_version(),
"Version mismatch: django_downloadview.__version__ "
'is "%s" whereas pkg_resources tells "%s". '
"You may need to run ``make develop`` to update the "
"installed version in development environment."
% (self.get_version(), installed_version),
)
def test_version_file(self):
"""django_downloadview.__version__ matches VERSION file info."""
version_file = os.path.join(project_dir, 'VERSION')
version_file = os.path.join(project_dir, "VERSION")
file_version = open(version_file).read().strip()
self.assertEqual(file_version, self.get_version(),
'Version mismatch: django_downloadview.__version__ '
'is "%s" whereas VERSION file tells "%s". '
'You may need to run ``make develop`` to update the '
'installed version in development environment.'
% (self.get_version(), file_version))
self.assertEqual(
file_version,
self.get_version(),
"Version mismatch: django_downloadview.__version__ "
'is "%s" whereas VERSION file tells "%s". '
"You may need to run ``make develop`` to update the "
"installed version in development environment."
% (self.get_version(), file_version),
)

View file

@ -6,13 +6,14 @@ from django_downloadview.response import DownloadResponse
class DownloadResponseTestCase(unittest.TestCase):
"""Tests around :class:`django_downloadviews.response.DownloadResponse`."""
def test_content_disposition_encoding(self):
"""Content-Disposition header is encoded."""
response = DownloadResponse('fake file',
attachment=True,
basename=u'espacé .txt',)
response = DownloadResponse(
"fake file", attachment=True, basename="espacé .txt",
)
headers = response.default_headers
self.assertIn("filename=\"espace_.txt\"",
headers['Content-Disposition'])
self.assertIn("filename*=UTF-8''espac%C3%A9%20.txt",
headers['Content-Disposition'])
self.assertIn('filename="espace_.txt"', headers["Content-Disposition"])
self.assertIn(
"filename*=UTF-8''espac%C3%A9%20.txt", headers["Content-Disposition"]
)

View file

@ -8,9 +8,10 @@ from django_downloadview.shortcuts import sendfile
class SendfileTestCase(django.test.TestCase):
"""Tests around :func:`django_downloadview.sendfile.sendfile`."""
def test_defaults(self):
"""sendfile() takes at least request and filename."""
request = django.test.RequestFactory().get('/fake-url')
request = django.test.RequestFactory().get("/fake-url")
filename = __file__
response = sendfile(request, filename)
self.assertTrue(isinstance(response, DownloadResponse))
@ -18,24 +19,25 @@ class SendfileTestCase(django.test.TestCase):
def test_custom(self):
"""sendfile() accepts various arguments for response tuning."""
request = django.test.RequestFactory().get('/fake-url')
request = django.test.RequestFactory().get("/fake-url")
filename = __file__
response = sendfile(request,
filename,
attachment=True,
attachment_filename='toto.txt',
mimetype='test/octet-stream',
encoding='gzip')
response = sendfile(
request,
filename,
attachment=True,
attachment_filename="toto.txt",
mimetype="test/octet-stream",
encoding="gzip",
)
self.assertTrue(isinstance(response, DownloadResponse))
self.assertTrue(response.attachment)
self.assertEqual(response.basename, 'toto.txt')
self.assertEqual(response['Content-Type'],
'test/octet-stream; charset=utf-8')
self.assertEqual(response.get_encoding(), 'gzip')
self.assertEqual(response.basename, "toto.txt")
self.assertEqual(response["Content-Type"], "test/octet-stream; charset=utf-8")
self.assertEqual(response.get_encoding(), "gzip")
def test_404(self):
"""sendfile() raises Http404 if file does not exists."""
request = django.test.RequestFactory().get('/fake-url')
filename = 'i-do-no-exist'
request = django.test.RequestFactory().get("/fake-url")
filename = "i-do-no-exist"
with self.assertRaises(Http404):
sendfile(request, filename)

View file

@ -1,25 +1,22 @@
"""Tests around :mod:`django_downloadview.views`."""
import calendar
from datetime import datetime
import os
import unittest
from datetime import datetime
try:
from unittest import mock
except ImportError:
import mock
from unittest import mock
from django.core.files import File
from django.http import Http404
from django.http.response import HttpResponseNotModified
import django.test
from django_downloadview import exceptions
from django_downloadview import exceptions, views
from django_downloadview.test import setup_view
from django_downloadview import views
class DownloadMixinTestCase(unittest.TestCase):
"""Test suite around :class:`django_downloadview.views.DownloadMixin`."""
def test_get_file(self):
"""DownloadMixin.get_file() raise NotImplementedError.
@ -34,14 +31,15 @@ class DownloadMixinTestCase(unittest.TestCase):
"""DownloadMixin.get_basename() returns basename attribute."""
mixin = views.DownloadMixin()
self.assertEqual(mixin.get_basename(), None)
mixin.basename = 'fake'
self.assertEqual(mixin.get_basename(), 'fake')
mixin.basename = "fake"
self.assertEqual(mixin.get_basename(), "fake")
def test_was_modified_since_specific(self):
"""DownloadMixin.was_modified_since() delegates to file wrapper."""
file_wrapper = mock.Mock()
file_wrapper.was_modified_since = mock.Mock(
return_value=mock.sentinel.return_value)
return_value=mock.sentinel.return_value
)
mixin = views.DownloadMixin()
since = mock.sentinel.since
return_value = mixin.was_modified_since(file_wrapper, since)
@ -51,13 +49,14 @@ class DownloadMixinTestCase(unittest.TestCase):
def test_was_modified_since_not_implemented(self):
"""DownloadMixin.was_modified_since() returns True if file wrapper
does not support ``modified_time`` or ``size`` attributes."""
fields = ['modified_time', 'size']
side_effects = [AttributeError('fake'), NotImplementedError('fake')]
fields = ["modified_time", "size"]
side_effects = [AttributeError("fake"), NotImplementedError("fake")]
for field in fields:
for side_effect in side_effects:
file_wrapper = mock.Mock()
setattr(file_wrapper, field, mock.Mock(
side_effect=AttributeError('fake')))
setattr(
file_wrapper, field, mock.Mock(side_effect=AttributeError("fake"))
)
mixin = views.DownloadMixin()
since = mock.sentinel.since
self.assertTrue(mixin.was_modified_since(file_wrapper, since))
@ -71,13 +70,14 @@ class DownloadMixinTestCase(unittest.TestCase):
"""
file_wrapper = mock.Mock()
file_wrapper.was_modified_since = mock.Mock(
return_value=mock.sentinel.was_modified)
return_value=mock.sentinel.was_modified
)
mixin = views.DownloadMixin()
self.assertIs(
mixin.was_modified_since(file_wrapper, mock.sentinel.since),
mock.sentinel.was_modified)
file_wrapper.was_modified_since.assert_called_once_with(
mock.sentinel.since)
mock.sentinel.was_modified,
)
file_wrapper.was_modified_since.assert_called_once_with(mock.sentinel.since)
def test_was_modified_since_django(self):
"""DownloadMixin.was_modified_since() tries (2) files attributes.
@ -90,22 +90,24 @@ class DownloadMixinTestCase(unittest.TestCase):
"""
file_wrapper = mock.Mock()
file_wrapper.was_modified_since = mock.Mock(
side_effect=AttributeError)
file_wrapper.was_modified_since = mock.Mock(side_effect=AttributeError)
file_wrapper.size = mock.sentinel.size
file_wrapper.modified_time = datetime.now()
was_modified_since_mock = mock.Mock(
return_value=mock.sentinel.was_modified)
was_modified_since_mock = mock.Mock(return_value=mock.sentinel.was_modified)
mixin = views.DownloadMixin()
with mock.patch('django_downloadview.views.base.was_modified_since',
new=was_modified_since_mock):
with mock.patch(
"django_downloadview.views.base.was_modified_since",
new=was_modified_since_mock,
):
self.assertIs(
mixin.was_modified_since(file_wrapper, mock.sentinel.since),
mock.sentinel.was_modified)
mock.sentinel.was_modified,
)
was_modified_since_mock.assert_called_once_with(
mock.sentinel.since,
calendar.timegm(file_wrapper.modified_time.utctimetuple()),
mock.sentinel.size)
mock.sentinel.size,
)
def test_was_modified_since_fallback(self):
"""DownloadMixin.was_modified_since() fallbacks to `True`.
@ -124,14 +126,12 @@ class DownloadMixinTestCase(unittest.TestCase):
"""
file_wrapper = mock.Mock()
file_wrapper.was_modified_since = mock.Mock(
side_effect=NotImplementedError)
file_wrapper.was_modified_since = mock.Mock(side_effect=NotImplementedError)
type(file_wrapper).modified_time = mock.PropertyMock(
side_effect=NotImplementedError)
side_effect=NotImplementedError
)
mixin = views.DownloadMixin()
self.assertIs(
mixin.was_modified_since(file_wrapper, 'fake since'),
True)
self.assertIs(mixin.was_modified_since(file_wrapper, "fake since"), True)
def test_not_modified_response(self):
"DownloadMixin.not_modified_response returns HttpResponseNotModified."
@ -145,12 +145,14 @@ class DownloadMixinTestCase(unittest.TestCase):
mixin.file_instance = mock.sentinel.file_wrapper
response_factory = mock.Mock(return_value=mock.sentinel.response)
mixin.response_class = response_factory
response_kwargs = {'dummy': 'value',
'file_instance': mock.sentinel.file_wrapper,
'attachment': True,
'basename': None,
'file_mimetype': None,
'file_encoding': None}
response_kwargs = {
"dummy": "value",
"file_instance": mock.sentinel.file_wrapper,
"attachment": True,
"basename": None,
"file_mimetype": None,
"file_encoding": None,
}
response = mixin.download_response(**response_kwargs)
self.assertIs(response, mock.sentinel.response)
response_factory.assert_called_once_with(**response_kwargs) # Not args
@ -161,11 +163,12 @@ class DownloadMixinTestCase(unittest.TestCase):
# Setup.
mixin = views.DownloadMixin()
mixin.request = django.test.RequestFactory().get(
'/dummy-url',
HTTP_IF_MODIFIED_SINCE=mock.sentinel.http_if_modified_since)
"/dummy-url", HTTP_IF_MODIFIED_SINCE=mock.sentinel.http_if_modified_since
)
mixin.was_modified_since = mock.Mock(return_value=False)
mixin.not_modified_response = mock.Mock(
return_value=mock.sentinel.http_not_modified_response)
return_value=mock.sentinel.http_not_modified_response
)
mixin.get_file = mock.Mock(return_value=mock.sentinel.file_wrapper)
# Run.
response = mixin.render_to_response()
@ -173,8 +176,8 @@ class DownloadMixinTestCase(unittest.TestCase):
self.assertIs(response, mock.sentinel.http_not_modified_response)
mixin.get_file.assert_called_once_with()
mixin.was_modified_since.assert_called_once_with(
mock.sentinel.file_wrapper,
mock.sentinel.http_if_modified_since)
mock.sentinel.file_wrapper, mock.sentinel.http_if_modified_since
)
mixin.not_modified_response.assert_called_once_with()
def test_render_to_response_modified(self):
@ -182,11 +185,12 @@ class DownloadMixinTestCase(unittest.TestCase):
# Setup.
mixin = views.DownloadMixin()
mixin.request = django.test.RequestFactory().get(
'/dummy-url',
HTTP_IF_MODIFIED_SINCE=None)
"/dummy-url", HTTP_IF_MODIFIED_SINCE=None
)
mixin.was_modified_since = mock.Mock()
mixin.download_response = mock.Mock(
return_value=mock.sentinel.download_response)
return_value=mock.sentinel.download_response
)
mixin.get_file = mock.Mock(return_value=mock.sentinel.file_wrapper)
# Run.
response = mixin.render_to_response()
@ -200,7 +204,7 @@ class DownloadMixinTestCase(unittest.TestCase):
"DownloadMixin.render_to_response() calls file_not_found_response()."
# Setup.
mixin = views.DownloadMixin()
mixin.request = django.test.RequestFactory().get('/dummy-url')
mixin.request = django.test.RequestFactory().get("/dummy-url")
mixin.get_file = mock.Mock(side_effect=exceptions.FileNotFound)
mixin.file_not_found_response = mock.Mock()
# Run.
@ -217,14 +221,14 @@ class DownloadMixinTestCase(unittest.TestCase):
class BaseDownloadViewTestCase(unittest.TestCase):
"Tests around :class:`django_downloadviews.views.base.BaseDownloadView`."
def test_get(self):
"""BaseDownloadView.get() calls render_to_response()."""
request = django.test.RequestFactory().get('/dummy-url')
args = ['dummy-arg']
kwargs = {'dummy': 'kwarg'}
request = django.test.RequestFactory().get("/dummy-url")
args = ["dummy-arg"]
kwargs = {"dummy": "kwarg"}
view = setup_view(views.BaseDownloadView(), request, *args, **kwargs)
view.render_to_response = mock.Mock(
return_value=mock.sentinel.response)
view.render_to_response = mock.Mock(return_value=mock.sentinel.response)
response = view.get(request, *args, **kwargs)
self.assertIs(response, mock.sentinel.response)
view.render_to_response.assert_called_once_with()
@ -232,10 +236,10 @@ class BaseDownloadViewTestCase(unittest.TestCase):
class PathDownloadViewTestCase(unittest.TestCase):
"Tests for :class:`django_downloadviews.views.path.PathDownloadView`."
def test_get_file_ok(self):
"PathDownloadView.get_file() returns ``File`` instance."
view = setup_view(views.PathDownloadView(path=__file__),
'fake request')
view = setup_view(views.PathDownloadView(path=__file__), "fake request")
file_wrapper = view.get_file()
self.assertTrue(isinstance(file_wrapper, File))
@ -244,8 +248,7 @@ class PathDownloadViewTestCase(unittest.TestCase):
exist.
"""
view = setup_view(views.PathDownloadView(path='i-do-no-exist'),
'fake request')
view = setup_view(views.PathDownloadView(path="i-do-no-exist"), "fake request")
with self.assertRaises(exceptions.FileNotFound):
view.get_file()
@ -253,18 +256,19 @@ class PathDownloadViewTestCase(unittest.TestCase):
"""PathDownloadView.get_file() raises FileNotFound if file is a
directory."""
view = setup_view(
views.PathDownloadView(path=os.path.dirname(__file__)),
'fake request')
views.PathDownloadView(path=os.path.dirname(__file__)), "fake request"
)
with self.assertRaises(exceptions.FileNotFound):
view.get_file()
class ObjectDownloadViewTestCase(unittest.TestCase):
"Tests for :class:`django_downloadviews.views.object.ObjectDownloadView`."
def test_get_file_ok(self):
"ObjectDownloadView.get_file() returns ``file`` field by default."
view = setup_view(views.ObjectDownloadView(), 'fake request')
view.object = mock.Mock(spec=['file'])
view = setup_view(views.ObjectDownloadView(), "fake request")
view.object = mock.Mock(spec=["file"])
view.get_file()
def test_get_file_wrong_field(self):
@ -275,17 +279,19 @@ class ObjectDownloadViewTestCase(unittest.TestCase):
i.e. it is related to Python code.
"""
view = setup_view(views.ObjectDownloadView(file_field='other_field'),
'fake request')
view.object = mock.Mock(spec=['file'])
view = setup_view(
views.ObjectDownloadView(file_field="other_field"), "fake request"
)
view.object = mock.Mock(spec=["file"])
with self.assertRaises(AttributeError):
view.get_file()
def test_get_file_empty_field(self):
"""ObjectDownloadView.get_file() raises FileNotFound if field does not
exist."""
view = setup_view(views.ObjectDownloadView(file_field='other_field'),
'fake request')
view = setup_view(
views.ObjectDownloadView(file_field="other_field"), "fake request"
)
view.object = mock.Mock()
view.object.other_field = None
with self.assertRaises(exceptions.FileNotFound):
@ -295,13 +301,15 @@ class ObjectDownloadViewTestCase(unittest.TestCase):
class VirtualDownloadViewTestCase(unittest.TestCase):
"""Test suite around
:py:class:`django_downloadview.views.VirtualDownloadView`."""
def test_was_modified_since_specific(self):
"""VirtualDownloadView.was_modified_since() delegates to file wrapper.
"""
file_wrapper = mock.Mock()
file_wrapper.was_modified_since = mock.Mock(
return_value=mock.sentinel.from_file_wrapper)
return_value=mock.sentinel.from_file_wrapper
)
view = views.VirtualDownloadView()
since = mock.sentinel.since
return_value = view.was_modified_since(file_wrapper, since)
@ -314,9 +322,9 @@ class VirtualDownloadViewTestCase(unittest.TestCase):
file_wrapper = mock.Mock()
file_wrapper.was_modified_since = mock.Mock(side_effect=AttributeError)
modified_time = mock.PropertyMock()
setattr(file_wrapper, 'modified_time', modified_time)
setattr(file_wrapper, "modified_time", modified_time)
size = mock.PropertyMock()
setattr(file_wrapper, 'size', size)
setattr(file_wrapper, "size", size)
view = views.VirtualDownloadView()
since = mock.sentinel.since
result = view.was_modified_since(file_wrapper, since)

View file

@ -24,8 +24,12 @@ commands =
[testenv:flake8]
deps =
flake8
black
isort
commands =
flake8 demo django_downloadview tests
black --check demo django_downloadview tests
isort --check-only --recursive demo django_downloadview tests
[testenv:sphinx]
deps =
@ -55,4 +59,5 @@ commands =
fullrelease
[flake8]
max-line-length = 99
max-line-length = 88
ignore = E203, W503