Refs #23 - Implemented and tested PathDownloadView. This PathDownloadView is to replace an use case of former DownloadView.

This commit is contained in:
Benoît Bryon 2013-02-06 11:01:33 +01:00
parent b8d51c12fb
commit cc3df73905
8 changed files with 148 additions and 41 deletions

View file

@ -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)

View file

@ -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<slug>[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<path>[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<slug>[a-zA-Z0-9_-]+)/$',
'download_document',
name='document'),
)

View file

@ -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."""

View file

@ -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',
]

View file

@ -7,9 +7,18 @@
<h1>Welcome to django-downloadview demo!</h1>
<p>Here are some demo links. Browse the code to see how they are implemented</p>
<ul>
<li><a href="{% url 'download_hello_world' %}">DownloadView</a></li>
<li><a href="{% url 'download_document' 'hello-world' %}">ObjectDownloadView</a></li>
<li><a href="{% url 'download_document_nginx' 'hello-world' %}">ObjectDownloadView decorated with nginx X-Accel-Redirect</a> (better if served behind nginx)</li>
<li><a href="{% url 'hello_world' %}">
Direct download to one file using PathDownloadView.
</a></li>
<li><a href="{% url 'fixture_from_path' 'hello-world.txt' %}">
Download files using PathDownloadView and relative path in URL.
</a></li>
<li><a href="{% url 'document' 'hello-world' %}">
ObjectDownloadView
</a></li>
<li><a href="{% url 'download_document_nginx' 'hello-world' %}">
ObjectDownloadView decorated with nginx X-Accel-Redirect
</a> to be served behind Nginx</li>
</ul>
</body>
</html>

13
demo/demoproject/tests.py Normal file
View file

@ -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)

View file

@ -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')
)

View file

@ -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