From cc3df73905bea8fc9b979237e587dc563c4c2b9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Bryon?= Date: Wed, 6 Feb 2013 11:01:33 +0100 Subject: [PATCH] Refs #23 - Implemented and tested PathDownloadView. This PathDownloadView is to replace an use case of former DownloadView. --- demo/demoproject/download/tests.py | 40 ++++++++++++++---------- demo/demoproject/download/urls.py | 21 +++++++++---- demo/demoproject/download/views.py | 46 +++++++++++++++++++++++++--- demo/demoproject/settings.py | 7 +++-- demo/demoproject/templates/home.html | 15 +++++++-- demo/demoproject/tests.py | 13 ++++++++ demo/demoproject/urls.py | 5 +-- django_downloadview/views.py | 42 +++++++++++++++++++++---- 8 files changed, 148 insertions(+), 41 deletions(-) create mode 100644 demo/demoproject/tests.py diff --git a/demo/demoproject/download/tests.py b/demo/demoproject/download/tests.py index 867c651..246ccf7 100644 --- a/demo/demoproject/download/tests.py +++ b/demo/demoproject/download/tests.py @@ -4,7 +4,7 @@ from os import listdir from os.path import abspath, dirname, join from django.core.files import File -from django.core.urlresolvers import reverse_lazy as reverse +from django.core.urlresolvers import reverse from django.test import TestCase from django_downloadview.test import temporary_media_root @@ -25,13 +25,8 @@ class DownloadTestCase(TestCase): for f in listdir(fixtures_dir): self.files[f] = abspath(join(fixtures_dir, f)) - -class DownloadViewTestCase(DownloadTestCase): - """Test generic DownloadView.""" - def test_download_hello_world(self): - """download_hello_world view returns hello-world.txt as attachement.""" - download_url = reverse('download_hello_world') - response = self.client.get(download_url) + def assertDownloadHelloWorld(self, response, is_attachment=True): + """Assert response is 'hello-world.txt' download.""" self.assertEquals(response.status_code, 200) self.assertEquals(response['Content-Type'], 'text/plain; charset=utf-8') @@ -42,21 +37,32 @@ class DownloadViewTestCase(DownloadTestCase): response.content) +class PathDownloadViewTestCase(DownloadTestCase): + """Test "hello_world" view.""" + def test_download_hello_world(self): + """hello_world view returns hello-world.txt as attachement.""" + download_url = reverse('hello_world') + response = self.client.get(download_url) + self.assertDownloadHelloWorld(response) + + +class CustomPathDownloadViewTestCase(DownloadTestCase): + """Test "fixture_from_path" view.""" + def test_download_hello_world(self): + """fixture_from_path view can return hello-world.txt as attachement.""" + download_url = reverse('fixture_from_path', args=['hello-world.txt']) + response = self.client.get(download_url) + self.assertDownloadHelloWorld(response) + + class ObjectDownloadViewTestCase(DownloadTestCase): """Test generic ObjectDownloadView.""" @temporary_media_root() def test_download_hello_world(self): """'download_document' view returns hello-world.txt as attachement.""" slug = 'hello-world' - download_url = reverse('download_document', kwargs={'slug': slug}) + download_url = reverse('document', kwargs={'slug': slug}) Document.objects.create(slug=slug, file=File(open(self.files['hello-world.txt']))) response = self.client.get(download_url) - self.assertEquals(response.status_code, 200) - self.assertEquals(response['Content-Type'], - 'text/plain; charset=utf-8') - self.assertFalse('ContentEncoding' in response) - self.assertEquals(response['Content-Disposition'], - 'attachment; filename=hello-world.txt') - self.assertEqual(open(self.files['hello-world.txt']).read(), - response.content) + self.assertDownloadHelloWorld(response) diff --git a/demo/demoproject/download/urls.py b/demo/demoproject/download/urls.py index 53e4296..234d974 100644 --- a/demo/demoproject/download/urls.py +++ b/demo/demoproject/download/urls.py @@ -1,10 +1,19 @@ +# coding=utf8 """URL mapping.""" -from django.conf.urls import patterns, include, url +from django.conf.urls import patterns, url -urlpatterns = patterns('demoproject.download.views', - url(r'^hello-world\.txt$', 'download_hello_world', - name='download_hello_world'), - url(r'^document/(?P[a-zA-Z0-9_-]+)/$', 'download_document', - name='download_document'), +urlpatterns = patterns( + 'demoproject.download.views', + # Path-based downloads. + url(r'^hello-world\.txt$', + 'download_hello_world', + name='hello_world'), + url(r'^path/(?P[a-zA-Z0-9_-]+\.[a-zA-Z0-9]{1,4})$', + 'download_fixture_from_path', + name='fixture_from_path'), + # Model-based downloads. + url(r'^document/(?P[a-zA-Z0-9_-]+)/$', + 'download_document', + name='document'), ) diff --git a/demo/demoproject/download/views.py b/demo/demoproject/download/views.py index 6ba9854..44e3315 100644 --- a/demo/demoproject/download/views.py +++ b/demo/demoproject/download/views.py @@ -1,16 +1,54 @@ +# coding=utf8 +"""Demo download views.""" from os.path import abspath, dirname, join -from django_downloadview import DownloadView, ObjectDownloadView +from django_downloadview.views import ObjectDownloadView, PathDownloadView from demoproject.download.models import Document +# Some initializations. + app_dir = dirname(abspath(__file__)) +"""Directory containing code of :py:module:`demoproject.download.views`.""" + fixtures_dir = join(app_dir, 'fixtures') -hello_world_file = join(fixtures_dir, 'hello-world.txt') +"""Directory containing files fixtures.""" + +hello_world_path = join(fixtures_dir, 'hello-world.txt') +"""Path to a text file that says 'Hello world!'.""" -download_hello_world = DownloadView.as_view(filename=hello_world_file, - storage=None) +# Here are the views. + +download_hello_world = PathDownloadView.as_view(path=hello_world_path) +"""Direct download of one file, based on an absolute path. + +You could use this example as a shortcut, inside other views. + +""" + + +class CustomPathDownloadView(PathDownloadView): + """Example of customized PathDownloadView.""" + def get_path(self): + """Convert relative path (provided in URL) into absolute path. + + Notice that this particularly simple use case is covered by + :py:class:`django_downloadview.views.StorageDownloadView`. + + .. warning:: + + If you are doing such things, make the path secure! Prevent users + to download files anywhere in the filesystem. + + """ + path = super(CustomPathDownloadView, self).get_path() + return join(fixtures_dir, path) + +download_fixture_from_path = CustomPathDownloadView.as_view() +"""Pre-configured :py:class:`CustomPathDownloadView`.""" + download_document = ObjectDownloadView.as_view(model=Document) +"""Pre-configured download view for :py:class:`Document` model.""" diff --git a/demo/demoproject/settings.py b/demo/demoproject/settings.py index 57ccda1..377f740 100755 --- a/demo/demoproject/settings.py +++ b/demo/demoproject/settings.py @@ -65,14 +65,14 @@ MIDDLEWARE_CLASSES = [ #NGINX_DOWNLOAD_MIDDLEWARE_MEDIA_URL = "/proxied-download" -# Development configuratio. +# Development configuration. DEBUG = True TEMPLATE_DEBUG = DEBUG TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' NOSE_ARGS = ['--verbose', '--nocapture', '--rednose', - '--with-id', # allows --failed which only reruns failed tests + '--with-id', # allows --failed which only reruns failed tests '--id-file=%s' % join(data_dir, 'test', 'noseids'), '--with-doctest', '--with-xunit', @@ -81,4 +81,5 @@ NOSE_ARGS = ['--verbose', '--cover-erase', '--cover-package=django_downloadview', '--no-path-adjustment', - ] + '--all-modules', + ] diff --git a/demo/demoproject/templates/home.html b/demo/demoproject/templates/home.html index 10caf22..4e499d6 100644 --- a/demo/demoproject/templates/home.html +++ b/demo/demoproject/templates/home.html @@ -7,9 +7,18 @@

Welcome to django-downloadview demo!

Here are some demo links. Browse the code to see how they are implemented

diff --git a/demo/demoproject/tests.py b/demo/demoproject/tests.py new file mode 100644 index 0000000..35e6304 --- /dev/null +++ b/demo/demoproject/tests.py @@ -0,0 +1,13 @@ +# coding=utf8 +"""Test suite for demoproject.download.""" +from django.core.urlresolvers import reverse +from django.test import TestCase + + +class HomeViewTestCase(TestCase): + """Test homepage.""" + def test_get(self): + """Homepage returns HTTP 200.""" + 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 d7597a1..d8ffdd0 100755 --- a/demo/demoproject/urls.py +++ b/demo/demoproject/urls.py @@ -5,11 +5,12 @@ from django.views.generic import TemplateView home = TemplateView.as_view(template_name='home.html') -urlpatterns = patterns('', +urlpatterns = patterns( + '', # Standard download views. url(r'^download/', include('demoproject.download.urls')), # Nginx optimizations. url(r'^nginx/', include('demoproject.nginx.urls')), - # An informative page. + # An informative homepage. url(r'', home, name='home') ) diff --git a/django_downloadview/views.py b/django_downloadview/views.py index 9792d82..7baa51b 100644 --- a/django_downloadview/views.py +++ b/django_downloadview/views.py @@ -68,18 +68,48 @@ class BaseDownloadView(DownloadMixin, View): return self.render_to_response() +class PathDownloadView(BaseDownloadView): + """Serve a file using filename.""" + path = None + """Server-side name (including path) of the file to serve. + + Filename is supposed to be an absolute filename of a file located on the + local filesystem. + + """ + + path_url_kwarg = 'path' + """Name of the URL argument that contains path.""" + + def get_path(self): + """Return actual path of the file to serve. + + Default implementation simply returns view's :py:attr:`path`. + + Override this method if you want custom implementation. + As an example, :py:attr:`path` could be relative and your custom + :py:meth:`get_path` implementation makes it absolute. + + """ + return self.kwargs.get(self.path_url_kwarg, self.path) + + def get_file(self): + """Use path to return wrapper around file to serve.""" + return File(open(self.get_path())) + + class StorageDownloadView(): - """Download a file from storage and filename.""" + """Serve a file using storage and filename.""" storage = DefaultStorage() path = None -class SimpleDownloadView(): - """Download a file from filename.""" - path = None - - class VirtualDownloadView(): + """Serve not-on-disk or generated-on-the-fly file. + + Use this class to serve :py:class:`StringIO` files. + + """ file_obj = None