diff --git a/Makefile b/Makefile index e1b1888..1d53049 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ # PIP = pip TOX = tox +BLACK = black #: help - Display callable targets. @@ -96,3 +97,7 @@ runserver: demo .PHONY: release release: $(TOX) -e release + +.PHONY: black +black: + $(BLACK) demo tests django_downloadview diff --git a/demo/demoproject/apache/tests.py b/demo/demoproject/apache/tests.py index b5c9bb6..2635f1a 100644 --- a/demo/demoproject/apache/tests.py +++ b/demo/demoproject/apache/tests.py @@ -12,32 +12,34 @@ from demoproject.apache.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(u"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", + ) diff --git a/demo/demoproject/apache/urls.py b/demo/demoproject/apache/urls.py index 7af509e..2c7b1c8 100644 --- a/demo/demoproject/apache/urls.py +++ b/demo/demoproject/apache/urls.py @@ -6,11 +6,15 @@ from demoproject.apache import views 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", + ), ) diff --git a/demo/demoproject/apache/views.py b/demo/demoproject/apache/views.py index cc4342e..e5c0f20 100644 --- a/demo/demoproject/apache/views.py +++ b/demo/demoproject/apache/views.py @@ -7,16 +7,19 @@ 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/", +) diff --git a/demo/demoproject/compat.py b/demo/demoproject/compat.py index 5e2dc71..a95f5d1 100644 --- a/demo/demoproject/compat.py +++ b/demo/demoproject/compat.py @@ -5,18 +5,22 @@ 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 diff --git a/demo/demoproject/http/tests.py b/demo/demoproject/http/tests.py index 7e6a1eb..dcb0f4e 100644 --- a/demo/demoproject/http/tests.py +++ b/demo/demoproject/http/tests.py @@ -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") diff --git a/demo/demoproject/http/urls.py b/demo/demoproject/http/urls.py index 6198c39..7e7fe87 100644 --- a/demo/demoproject/http/urls.py +++ b/demo/demoproject/http/urls.py @@ -5,11 +5,7 @@ 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"), ) diff --git a/demo/demoproject/http/views.py b/demo/demoproject/http/views.py index 8f174f2..424c086 100644 --- a/demo/demoproject/http/views.py +++ b/demo/demoproject/http/views.py @@ -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() diff --git a/demo/demoproject/lighttpd/tests.py b/demo/demoproject/lighttpd/tests.py index 2a9fc89..45b3f7c 100644 --- a/demo/demoproject/lighttpd/tests.py +++ b/demo/demoproject/lighttpd/tests.py @@ -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(u"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", + ) diff --git a/demo/demoproject/lighttpd/urls.py b/demo/demoproject/lighttpd/urls.py index c300ec1..72c6725 100644 --- a/demo/demoproject/lighttpd/urls.py +++ b/demo/demoproject/lighttpd/urls.py @@ -6,11 +6,15 @@ 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", + ), ) diff --git a/demo/demoproject/lighttpd/views.py b/demo/demoproject/lighttpd/views.py index e2eea86..61363e8 100644 --- a/demo/demoproject/lighttpd/views.py +++ b/demo/demoproject/lighttpd/views.py @@ -7,17 +7,19 @@ 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/", +) diff --git a/demo/demoproject/manage.py b/demo/demoproject/manage.py index b4f6b77..fa2446e 100755 --- a/demo/demoproject/manage.py +++ b/demo/demoproject/manage.py @@ -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) diff --git a/demo/demoproject/nginx/tests.py b/demo/demoproject/nginx/tests.py index b8e599b..582adb7 100644 --- a/demo/demoproject/nginx/tests.py +++ b/demo/demoproject/nginx/tests.py @@ -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(u"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, + ) diff --git a/demo/demoproject/nginx/urls.py b/demo/demoproject/nginx/urls.py index 874a7a7..4f50bab 100644 --- a/demo/demoproject/nginx/urls.py +++ b/demo/demoproject/nginx/urls.py @@ -7,11 +7,15 @@ 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", + ), ) diff --git a/demo/demoproject/nginx/views.py b/demo/demoproject/nginx/views.py index e8a44c9..73ff734 100644 --- a/demo/demoproject/nginx/views.py +++ b/demo/demoproject/nginx/views.py @@ -7,16 +7,19 @@ 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/", +) diff --git a/demo/demoproject/object/models.py b/demo/demoproject/object/models.py index bd55215..6d080c9 100644 --- a/demo/demoproject/object/models.py +++ b/demo/demoproject/object/models.py @@ -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) diff --git a/demo/demoproject/object/tests.py b/demo/demoproject/object/tests.py index 2530d3b..8e32b1a 100644 --- a/demo/demoproject/object/tests.py +++ b/demo/demoproject/object/tests.py @@ -8,22 +8,18 @@ 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 +29,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 +45,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 +61,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 +77,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, + ) diff --git a/demo/demoproject/object/urls.py b/demo/demoproject/object/urls.py index ee3f54c..f99b274 100644 --- a/demo/demoproject/object/urls.py +++ b/demo/demoproject/object/urls.py @@ -5,17 +5,25 @@ from demoproject.object import views urlpatterns = patterns( - '', - url(r'^default-file/(?P[a-zA-Z0-9_-]+)/$', + "", + url( + r"^default-file/(?P[a-zA-Z0-9_-]+)/$", views.default_file_view, - name='default_file'), - url(r'^another-file/(?P[a-zA-Z0-9_-]+)/$', + name="default_file", + ), + url( + r"^another-file/(?P[a-zA-Z0-9_-]+)/$", views.another_file_view, - name='another_file'), - url(r'^deserialized_basename/(?P[a-zA-Z0-9_-]+)/$', + name="another_file", + ), + url( + r"^deserialized_basename/(?P[a-zA-Z0-9_-]+)/$", views.deserialized_basename_view, - name='deserialized_basename'), - url(r'^inline-file/(?P[a-zA-Z0-9_-]+)/$', + name="deserialized_basename", + ), + url( + r"^inline-file/(?P[a-zA-Z0-9_-]+)/$", views.inline_file_view, - name='inline_file'), + name="inline_file", + ), ) diff --git a/demo/demoproject/object/views.py b/demo/demoproject/object/views.py index 2675a6a..ed8020e 100644 --- a/demo/demoproject/object/views.py +++ b/demo/demoproject/object/views.py @@ -8,16 +8,14 @@ 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) diff --git a/demo/demoproject/path/tests.py b/demo/demoproject/path/tests.py index 6fe25a8..cf51231 100644 --- a/demo/demoproject/path/tests.py +++ b/demo/demoproject/path/tests.py @@ -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", + ) diff --git a/demo/demoproject/path/urls.py b/demo/demoproject/path/urls.py index 161203d..a1812e2 100644 --- a/demo/demoproject/path/urls.py +++ b/demo/demoproject/path/urls.py @@ -5,11 +5,11 @@ from demoproject.path import views urlpatterns = patterns( - '', - url(r'^static-path/$', - views.static_path, - name='static_path'), - url(r'^dynamic-path/(?P[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[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{1,4})$", views.dynamic_path, - name='dynamic_path'), + name="dynamic_path", + ), ) diff --git a/demo/demoproject/path/views.py b/demo/demoproject/path/views.py index 66b438b..c17975d 100644 --- a/demo/demoproject/path/views.py +++ b/demo/demoproject/path/views.py @@ -6,9 +6,9 @@ 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 +27,7 @@ class DynamicPathDownloadView(PathDownloadView): :class:`StorageDownloadView` """ + def get_path(self): """Return path inside fixtures directory.""" # Get path from URL resolvers or as_view kwarg. diff --git a/demo/demoproject/settings.py b/demo/demoproject/settings.py index cb548a8..18d1a5f 100755 --- a/demo/demoproject/settings.py +++ b/demo/demoproject/settings.py @@ -11,20 +11,20 @@ from django.utils.version import get_version 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 +33,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 +97,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"),) diff --git a/demo/demoproject/storage/tests.py b/demo/demoproject/storage/tests.py index 3f0abc2..46d8bb0 100644 --- a/demo/demoproject/storage/tests.py +++ b/demo/demoproject/storage/tests.py @@ -13,7 +13,7 @@ from demoproject.storage import views # Fixtures. -file_content = 'Hello world!\n' +file_content = "Hello world!\n" def setup_file(path): @@ -24,44 +24,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 +78,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 +104,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") diff --git a/demo/demoproject/storage/urls.py b/demo/demoproject/storage/urls.py index 1932cd4..ff16051 100644 --- a/demo/demoproject/storage/urls.py +++ b/demo/demoproject/storage/urls.py @@ -5,11 +5,15 @@ from demoproject.storage import views urlpatterns = patterns( - '', - url(r'^static-path/(?P[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{1,4})$', + "", + url( + r"^static-path/(?P[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{1,4})$", views.static_path, - name='static_path'), - url(r'^dynamic-path/(?P[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{1,4})$', + name="static_path", + ), + url( + r"^dynamic-path/(?P[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{1,4})$", views.dynamic_path, - name='dynamic_path'), + name="dynamic_path", + ), ) diff --git a/demo/demoproject/storage/views.py b/demo/demoproject/storage/views.py index 51e7acc..e349f16 100644 --- a/demo/demoproject/storage/views.py +++ b/demo/demoproject/storage/views.py @@ -12,6 +12,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() diff --git a/demo/demoproject/tests.py b/demo/demoproject/tests.py index 9bfc783..b82b8f6 100644 --- a/demo/demoproject/tests.py +++ b/demo/demoproject/tests.py @@ -7,8 +7,9 @@ from django.test import TestCase 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) diff --git a/demo/demoproject/urls.py b/demo/demoproject/urls.py index 6ec45f7..9df4bfb 100755 --- a/demo/demoproject/urls.py +++ b/demo/demoproject/urls.py @@ -4,43 +4,45 @@ from django.views.generic import TemplateView from demoproject.compat import patterns, include -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"), ) diff --git a/demo/demoproject/virtual/tests.py b/demo/demoproject/virtual/tests.py index 63aa74e..fb3b164 100644 --- a/demo/demoproject/virtual/tests.py +++ b/demo/demoproject/virtual/tests.py @@ -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", + ) diff --git a/demo/demoproject/virtual/urls.py b/demo/demoproject/virtual/urls.py index 2ad7105..559695c 100644 --- a/demo/demoproject/virtual/urls.py +++ b/demo/demoproject/virtual/urls.py @@ -5,14 +5,8 @@ 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"), ) diff --git a/demo/demoproject/virtual/views.py b/demo/demoproject/virtual/views.py index ba4db32..0091c66 100644 --- a/demo/demoproject/virtual/views.py +++ b/demo/demoproject/virtual/views.py @@ -10,24 +10,24 @@ from django_downloadview import TextIteratorIO 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') + return VirtualFile(file_obj, name="hello-world.txt") def generate_hello(): - yield u'Hello ' - yield u'world!' - yield u'\n' + yield u"Hello " + yield u"world!" + yield u"\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") diff --git a/demo/setup.py b/demo/setup.py index 12f7051..dd4d858 100644 --- a/demo/setup.py +++ b/demo/setup.py @@ -9,40 +9,40 @@ 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 = 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", +] 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, + ) diff --git a/django_downloadview/__init__.py b/django_downloadview/__init__.py index 9a2d4d0..b5814d9 100644 --- a/django_downloadview/__init__.py +++ b/django_downloadview/__init__.py @@ -3,7 +3,7 @@ import pkg_resources #: Module version, as defined in PEP-0396. -__version__ = pkg_resources.get_distribution(__package__.replace('-', '_')).version +__version__ = pkg_resources.get_distribution(__package__.replace("-", "_")).version # API shortcuts. diff --git a/django_downloadview/apache/middlewares.py b/django_downloadview/apache/middlewares.py index 9f4b716..37bf5c4 100644 --- a/django_downloadview/apache/middlewares.py +++ b/django_downloadview/apache/middlewares.py @@ -1,6 +1,8 @@ from django_downloadview.apache.response import XSendfileResponse -from django_downloadview.middlewares import (ProxiedDownloadMiddleware, - NoRedirectionMatch) +from django_downloadview.middlewares import ( + ProxiedDownloadMiddleware, + NoRedirectionMatch, +) 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, + ) diff --git a/django_downloadview/apache/response.py b/django_downloadview/apache/response.py index 3eae968..5f02aed 100644 --- a/django_downloadview/apache/response.py +++ b/django_downloadview/apache/response.py @@ -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 diff --git a/django_downloadview/apache/tests.py b/django_downloadview/apache/tests.py index ee7910b..0792918 100644 --- a/django_downloadview/apache/tests.py +++ b/django_downloadview/apache/tests.py @@ -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) diff --git a/django_downloadview/api.py b/django_downloadview/api.py index 086b528..e584343 100644 --- a/django_downloadview/api.py +++ b/django_downloadview/api.py @@ -1,25 +1,30 @@ """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.io import BytesIteratorIO, TextIteratorIO # NoQA +from django_downloadview.files import StorageFile, VirtualFile, HTTPFile # NoQA +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.test import ( + assert_download_response, # NoQA + setup_view, + temporary_media_root, +) # Backward compatibility. diff --git a/django_downloadview/decorators.py b/django_downloadview/decorators.py index 5c070ba..bac77d0 100644 --- a/django_downloadview/decorators.py +++ b/django_downloadview/decorators.py @@ -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 diff --git a/django_downloadview/files.py b/django_downloadview/files.py index 146bfc9..c09cab8 100644 --- a/django_downloadview/files.py +++ b/django_downloadview/files.py @@ -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=u"", 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=u"", **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"] diff --git a/django_downloadview/io.py b/django_downloadview/io.py index 83649a7..c0415cc 100644 --- a/django_downloadview/io.py +++ b/django_downloadview/io.py @@ -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 = u"" 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 u"".join(chunks) def readline(self): chunks = [] while True: - i = self._left.find(u'\n') + i = self._left.find(u"\n") if i == -1: chunks.append(self._left) try: self._left = next(self._iter) except StopIteration: - self._left = u'' + self._left = u"" 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 u"".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) diff --git a/django_downloadview/lighttpd/middlewares.py b/django_downloadview/lighttpd/middlewares.py index 626f293..ceb70a7 100644 --- a/django_downloadview/lighttpd/middlewares.py +++ b/django_downloadview/lighttpd/middlewares.py @@ -1,6 +1,8 @@ from django_downloadview.lighttpd.response import XSendfileResponse -from django_downloadview.middlewares import (ProxiedDownloadMiddleware, - NoRedirectionMatch) +from django_downloadview.middlewares import ( + ProxiedDownloadMiddleware, + NoRedirectionMatch, +) 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, + ) diff --git a/django_downloadview/lighttpd/response.py b/django_downloadview/lighttpd/response.py index 09f7217..219ee99 100644 --- a/django_downloadview/lighttpd/response.py +++ b/django_downloadview/lighttpd/response.py @@ -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 diff --git a/django_downloadview/lighttpd/tests.py b/django_downloadview/lighttpd/tests.py index 751a2dc..a7cdb39 100644 --- a/django_downloadview/lighttpd/tests.py +++ b/django_downloadview/lighttpd/tests.py @@ -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)) diff --git a/django_downloadview/middlewares.py b/django_downloadview/middlewares.py index d84c68a..e16b784 100644 --- a/django_downloadview/middlewares.py +++ b/django_downloadview/middlewares.py @@ -14,10 +14,12 @@ from django.core.exceptions import ImproperlyConfigured 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 @@ -43,6 +45,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 +69,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 +90,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 +101,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 +117,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 +145,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 +155,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 +165,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 +184,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 +198,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 +206,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 +217,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("/"))) diff --git a/django_downloadview/nginx/__init__.py b/django_downloadview/nginx/__init__.py index d8e4853..58a1cf6 100644 --- a/django_downloadview/nginx/__init__.py +++ b/django_downloadview/nginx/__init__.py @@ -9,5 +9,4 @@ See also `Nginx X-accel documentation `_ and from django_downloadview.nginx.decorators import x_accel_redirect # 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) +from django_downloadview.nginx.middlewares import XAccelRedirectMiddleware # NoQA diff --git a/django_downloadview/nginx/middlewares.py b/django_downloadview/nginx/middlewares.py index 71fc31d..6fc730d 100644 --- a/django_downloadview/nginx/middlewares.py +++ b/django_downloadview/nginx/middlewares.py @@ -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 ( + ProxiedDownloadMiddleware, + NoRedirectionMatch, +) 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, + ) diff --git a/django_downloadview/nginx/response.py b/django_downloadview/nginx/response.py index 529c862..1785549 100644 --- a/django_downloadview/nginx/response.py +++ b/django_downloadview/nginx/response.py @@ -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" diff --git a/django_downloadview/nginx/settings.py b/django_downloadview/nginx/settings.py index 05f4d79..2375ad2 100644 --- a/django_downloadview/nginx/settings.py +++ b/django_downloadview/nginx/settings.py @@ -15,7 +15,7 @@ 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 +27,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 +53,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 +71,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 +89,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) diff --git a/django_downloadview/nginx/tests.py b/django_downloadview/nginx/tests.py index e43c881..d538b6a 100644 --- a/django_downloadview/nginx/tests.py +++ b/django_downloadview/nginx/tests.py @@ -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) diff --git a/django_downloadview/response.py b/django_downloadview/response.py index 5049e20..41ced52 100644 --- a/django_downloadview/response.py +++ b/django_downloadview/response.py @@ -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,13 @@ 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}\"; filename*=UTF-8''{utf8_filename}" class DownloadResponse(StreamingHttpResponse): @@ -115,9 +113,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 +135,9 @@ class DownloadResponse(StreamingHttpResponse): #: A :doc:`file wrapper instance `, 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 +148,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 +181,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 +214,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 diff --git a/django_downloadview/shortcuts.py b/django_downloadview/shortcuts.py index 241d8d5..780c507 100644 --- a/django_downloadview/shortcuts.py +++ b/django_downloadview/shortcuts.py @@ -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) diff --git a/django_downloadview/test.py b/django_downloadview/test.py index fdd05cc..9b355d5 100644 --- a/django_downloadview/test.py +++ b/django_downloadview/test.py @@ -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): diff --git a/django_downloadview/utils.py b/django_downloadview/utils.py index 36f32b1..b45a82c 100644 --- a/django_downloadview/utils.py +++ b/django_downloadview/utils.py @@ -2,7 +2,7 @@ import re -charset_pattern = re.compile(r'charset=(?P.+)$', re.I | re.U) +charset_pattern = re.compile(r"charset=(?P.+)$", re.I | re.U) def content_type_to_charset(content_type): @@ -15,7 +15,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 +29,7 @@ def url_basename(url, content_type): somefile.rst """ - return url.split('/')[-1] + return url.split("/")[-1] def import_member(import_string): @@ -41,6 +41,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) diff --git a/django_downloadview/views/__init__.py b/django_downloadview/views/__init__.py index 0466f3a..09ac9c1 100644 --- a/django_downloadview/views/__init__.py +++ b/django_downloadview/views/__init__.py @@ -1,7 +1,6 @@ """Views to stream files.""" # API shortcuts. -from django_downloadview.views.base import (DownloadMixin, # NoQA - BaseDownloadView) +from django_downloadview.views.base import DownloadMixin, BaseDownloadView # 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 diff --git a/django_downloadview/views/base.py b/django_downloadview/views/base.py index 4a30f4e..b3479c7 100644 --- a/django_downloadview/views/base.py +++ b/django_downloadview/views/base.py @@ -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() diff --git a/django_downloadview/views/http.py b/django_downloadview/views/http.py index b7316f9..128ce0c 100644 --- a/django_downloadview/views/http.py +++ b/django_downloadview/views/http.py @@ -7,8 +7,9 @@ from django_downloadview.views.base import BaseDownloadView class HTTPDownloadView(BaseDownloadView): """Proxy files that live on remote servers.""" + #: URL to download (the one we are proxying). - url = u'' + url = u"" #: 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() + ) diff --git a/django_downloadview/views/object.py b/django_downloadview/views/object.py index 5f74c5a..1477493 100644 --- a/django_downloadview/views/object.py +++ b/django_downloadview/views/object.py @@ -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 diff --git a/django_downloadview/views/path.py b/django_downloadview/views/path.py index e51081d..61327ca 100644 --- a/django_downloadview/views/path.py +++ b/django_downloadview/views/path.py @@ -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")) diff --git a/django_downloadview/views/storage.py b/django_downloadview/views/storage.py index 9e1900d..e87b1bd 100644 --- a/django_downloadview/views/storage.py +++ b/django_downloadview/views/storage.py @@ -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() diff --git a/django_downloadview/views/virtual.py b/django_downloadview/views/virtual.py index 0edd002..8de0093 100644 --- a/django_downloadview/views/virtual.py +++ b/django_downloadview/views/virtual.py @@ -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. diff --git a/tests/api.py b/tests/api.py index 8568279..252b56d 100644 --- a/tests/api.py +++ b/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)}.' + ) diff --git a/tests/io.py b/tests/io.py index 5da43e2..b5958b2 100644 --- a/tests/io.py +++ b/tests/io.py @@ -4,30 +4,31 @@ import unittest from django_downloadview import TextIteratorIO, BytesIteratorIO -HELLO_TEXT = u'Hello world!\né\n' -HELLO_BYTES = b'Hello world!\n\xc3\xa9\n' +HELLO_TEXT = u"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 u"Hello " + yield u"world!" + yield u"\n" + yield u"é" + yield u"\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 +42,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()) diff --git a/tests/packaging.py b/tests/packaging.py index e53465e..ffe50da 100644 --- a/tests/packaging.py +++ b/tests/packaging.py @@ -5,14 +5,16 @@ 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 +22,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), + ) diff --git a/tests/response.py b/tests/response.py index 6793f04..9b41ecc 100644 --- a/tests/response.py +++ b/tests/response.py @@ -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=u"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"] + ) diff --git a/tests/sendfile.py b/tests/sendfile.py index f8b6e81..21ee719 100644 --- a/tests/sendfile.py +++ b/tests/sendfile.py @@ -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) diff --git a/tests/views.py b/tests/views.py index 1fa3806..0d0d0d7 100644 --- a/tests/views.py +++ b/tests/views.py @@ -3,6 +3,7 @@ import calendar import os import unittest from datetime import datetime + try: from unittest import mock except ImportError: @@ -20,6 +21,7 @@ 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 +36,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 +54,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 +75,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 +95,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 +131,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 +150,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 +168,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 +181,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 +190,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 +209,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 +226,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 +241,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 +253,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 +261,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 +284,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 +306,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 +327,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)