mirror of
https://github.com/jazzband/django-downloadview.git
synced 2026-03-16 22:40:25 +00:00
commit
bdf0ba2188
75 changed files with 1050 additions and 890 deletions
16
.isort.cfg
Normal file
16
.isort.cfg
Normal 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
|
||||
11
Makefile
11
Makefile
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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/",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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/",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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/",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"),)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
from django.core.files.storage import FileSystemStorage
|
||||
|
||||
|
||||
storage = FileSystemStorage()
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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("/")))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
************************************
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
||||
*************
|
||||
|
|
|
|||
115
tests/api.py
115
tests/api.py
|
|
@ -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)}.'
|
||||
)
|
||||
|
|
|
|||
29
tests/io.py
29
tests/io.py
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
144
tests/views.py
144
tests/views.py
|
|
@ -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)
|
||||
|
|
|
|||
7
tox.ini
7
tox.ini
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue