From 2c1ad3c7302dfda3c64f0f8500d257e27b949c69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Bryon?= Date: Tue, 5 Nov 2013 11:44:48 +0100 Subject: [PATCH] ObjectDownloadView now inherits from SingleObjectMixin and BaseDownloadView. Added example for setup_view() in documentation. --- Makefile | 7 ++-- demo/demoproject/settings.py | 20 +++-------- demo/demoproject/storage/tests.py | 48 +++++++++++++++++++++------ django_downloadview/api.py | 1 + django_downloadview/test.py | 28 ++++++++++++++++ django_downloadview/tests/__init__.py | 16 --------- django_downloadview/tests/api.py | 1 + django_downloadview/tests/views.py | 2 +- django_downloadview/views/base.py | 12 +++---- django_downloadview/views/object.py | 10 ++++-- docs/testing.txt | 35 ++++++++++++------- etc/buildout.cfg | 4 +-- etc/{nose.cfg => nose/base.cfg} | 2 ++ etc/nose/demoproject.cfg | 4 +++ etc/nose/django_downloadview.cfg | 4 +++ 15 files changed, 126 insertions(+), 68 deletions(-) rename etc/{nose.cfg => nose/base.cfg} (74%) create mode 100644 etc/nose/demoproject.cfg create mode 100644 etc/nose/django_downloadview.cfg diff --git a/Makefile b/Makefile index f7386d1..242723b 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ DATA_DIR = $(ROOT_DIR)/var WGET = wget PYTHON = $(shell which python) PROJECT = $(shell $(PYTHON) -c "import setup; print setup.NAME") +PACKAGE = $(shell $(PYTHON) -c "import setup; print setup.PACKAGES[0]") BUILDOUT_CFG = $(ROOT_DIR)/etc/buildout.cfg BUILDOUT_DIR = $(ROOT_DIR)/lib/buildout BUILDOUT_VERSION = 1.7.0 @@ -51,17 +52,17 @@ test: test-app test-demo test-documentation test-app: - $(NOSE) -c $(ROOT_DIR)/etc/nose.cfg --with-coverage --cover-package=django_downloadview django_downloadview tests + $(NOSE) -c $(ROOT_DIR)/etc/nose/base.cfg -c $(ROOT_DIR)/etc/nose/$(PACKAGE).cfg mv $(ROOT_DIR)/.coverage $(ROOT_DIR)/var/test/app.coverage test-demo: - $(BIN_DIR)/demo test demo + $(BIN_DIR)/demo test --nose-verbosity=2 mv $(ROOT_DIR)/.coverage $(ROOT_DIR)/var/test/demo.coverage test-documentation: - $(NOSE) -c $(ROOT_DIR)/etc/nose.cfg sphinxcontrib.testbuild.tests + $(NOSE) -c $(ROOT_DIR)/etc/nose/base.cfg sphinxcontrib.testbuild.tests sphinx: diff --git a/demo/demoproject/settings.py b/demo/demoproject/settings.py index 56742e1..86e6110 100755 --- a/demo/demoproject/settings.py +++ b/demo/demoproject/settings.py @@ -8,6 +8,7 @@ demoproject_dir = dirname(abspath(__file__)) demo_dir = dirname(demoproject_dir) root_dir = dirname(demo_dir) data_dir = join(root_dir, 'var') +cfg_dir = join(root_dir, 'etc') # Mandatory settings. @@ -76,21 +77,10 @@ DOWNLOADVIEW_MIDDLEWARES = ( ) -# 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 - '--id-file=%s' % join(data_dir, 'test', 'noseids'), - '--with-doctest', - '--with-xunit', - '--xunit-file=%s' % join(data_dir, 'test', 'nosetests.xml'), - '--with-coverage', - '--cover-erase', - '--cover-package=django_downloadview', - '--no-path-adjustment', - '--all-modules', - ] +nose_cfg_dir = join(cfg_dir, 'nose') +NOSE_ARGS = ['--config={etc}/base.cfg'.format(etc=nose_cfg_dir), + '--config={etc}/{package}.cfg'.format(etc=nose_cfg_dir, + package=__package__)] diff --git a/demo/demoproject/storage/tests.py b/demo/demoproject/storage/tests.py index 4bcb064..1924f48 100644 --- a/demo/demoproject/storage/tests.py +++ b/demo/demoproject/storage/tests.py @@ -1,15 +1,13 @@ -try: - from unittest import mock -except ImportError: # Python 2.x fallback. - import mock +import unittest from django.core.files.base import ContentFile from django.core.urlresolvers import reverse import django.test -from django_downloadview import temporary_media_root, assert_download_response +from django_downloadview import assert_download_response, temporary_media_root +from django_downloadview import setup_view -from demoproject.storage.views import storage +from demoproject.storage import views # Fixtures. @@ -17,13 +15,13 @@ file_content = 'Hello world!\n' def setup_file(path): - storage.save(path, ContentFile(file_content)) + views.storage.save(path, ContentFile(file_content)) class StaticPathTestCase(django.test.TestCase): @temporary_media_root() def test_download_response(self): - """'static_path' streams file by path.""" + """'storage:static_path' streams file by path.""" setup_file('1.txt') url = reverse('storage:static_path', kwargs={'path': '1.txt'}) response = self.client.get(url) @@ -34,10 +32,20 @@ class StaticPathTestCase(django.test.TestCase): mime_type='text/plain') -class DynamicPathTestCase(django.test.TestCase): +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.""" + """'dynamic_path' streams file by generated path. + + As we use ``self.client``, this test involves the whole Django stack, + including settings, middlewares, decorators... So we need to setup a + file, the storage, and an URL. + + This test actually asserts the URL ``storage:dynamic_path`` streams a + file in storage. + + """ setup_file('1.TXT') url = reverse('storage:dynamic_path', kwargs={'path': '1.txt'}) response = self.client.get(url) @@ -46,3 +54,23 @@ class DynamicPathTestCase(django.test.TestCase): 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. + + Uses :func:`~django_downloadview.test.setup_view` to target only + overriden methods. + + This test does not involve URLconf, middlewares or decorators. It is + fast. It has clear scope. It does not assert ``storage:dynamic_path`` + URL works. It targets only custom ``DynamicStorageDownloadView`` class. + + """ + view = setup_view(views.DynamicStorageDownloadView(), + django.test.RequestFactory().get('/fake-url'), + path='dummy path') + path = view.get_path() + self.assertEqual(path, 'DUMMY PATH') diff --git a/django_downloadview/api.py b/django_downloadview/api.py index 2167762..5428535 100644 --- a/django_downloadview/api.py +++ b/django_downloadview/api.py @@ -18,4 +18,5 @@ from django_downloadview.views import (PathDownloadView, # NoQA DownloadMixin) from django_downloadview.sendfile import sendfile # NoQA from django_downloadview.test import (assert_download_response, # NoQA + setup_view, temporary_media_root) diff --git a/django_downloadview/test.py b/django_downloadview/test.py index 3b0cd2d..31482b3 100644 --- a/django_downloadview/test.py +++ b/django_downloadview/test.py @@ -8,6 +8,34 @@ from django.test.utils import override_settings from django_downloadview.middlewares import is_download_response +def setup_view(view, request, *args, **kwargs): + """Mimic ``as_view()``, but returns view instance. + + Use this function to get view instances on which you can run unit tests, + by testing specific methods. + + This is an early implementation of + https://code.djangoproject.com/ticket/20456 + + ``view`` + A view instance, such as ``TemplateView(template_name='dummy.html')``. + Initialization arguments are the same you would pass to ``as_view()``. + + ``request`` + A request object, typically built with + :class:`~django.test.client.RequestFactory`. + + ``args`` and ``kwargs`` + "URLconf" positional and keyword arguments, the same you would pass to + :func:`~django.core.urlresolvers.reverse`. + + """ + view.request = request + view.args = args + view.kwargs = kwargs + return view + + class temporary_media_root(override_settings): """Temporarily override settings.MEDIA_ROOT with a temporary directory. diff --git a/django_downloadview/tests/__init__.py b/django_downloadview/tests/__init__.py index c8896e3..0f38c3e 100644 --- a/django_downloadview/tests/__init__.py +++ b/django_downloadview/tests/__init__.py @@ -1,18 +1,2 @@ # -*- coding: utf-8 -*- """Unit tests.""" - - -def setup_view(view, request, *args, **kwargs): - """Mimic as_view() returned callable, but returns view instance. - - ``args`` and ``kwargs`` are the same you would pass to - :func:`~django.core.urlresolvers.reverse`. - - This is an early implementation of - https://code.djangoproject.com/ticket/20456 - - """ - view.request = request - view.args = args - view.kwargs = kwargs - return view diff --git a/django_downloadview/tests/api.py b/django_downloadview/tests/api.py index 5adc0d7..40bdbeb 100644 --- a/django_downloadview/tests/api.py +++ b/django_downloadview/tests/api.py @@ -54,6 +54,7 @@ class APITestCase(unittest.TestCase): 'DownloadDispatcherMiddleware', # Testing: 'assert_download_response', + 'setup_view', 'temporary_media_root', # Utilities: 'StringIteratorIO', diff --git a/django_downloadview/tests/views.py b/django_downloadview/tests/views.py index 12238ee..c539d16 100644 --- a/django_downloadview/tests/views.py +++ b/django_downloadview/tests/views.py @@ -9,7 +9,7 @@ except ImportError: from django.http.response import HttpResponseNotModified import django.test -from django_downloadview.tests import setup_view +from django_downloadview.test import setup_view from django_downloadview.views import base diff --git a/django_downloadview/views/base.py b/django_downloadview/views/base.py index 43869df..807c0c9 100644 --- a/django_downloadview/views/base.py +++ b/django_downloadview/views/base.py @@ -66,19 +66,19 @@ class DownloadMixin(object): else: return was_modified_since(since, modification_time, size) - def not_modified_response(self, **response_kwargs): + def not_modified_response(self, *response_args, **response_kwargs): """Return :class:`django.http.HttpResponseNotModified` instance.""" - return HttpResponseNotModified(**response_kwargs) + return HttpResponseNotModified(*response_args, **response_kwargs) - def download_response(self, **response_kwargs): + 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 = self.response_class(**response_kwargs) + response = self.response_class(*response_args, **response_kwargs) return response - def render_to_response(self, **response_kwargs): + def render_to_response(self, *response_args, **response_kwargs): """Return "download" response. Respects the "HTTP_IF_MODIFIED_SINCE" header if any. In that case, uses @@ -94,7 +94,7 @@ class DownloadMixin(object): if not self.was_modified_since(self.file_instance, since): return self.not_modified_response(**response_kwargs) # Return download response. - return self.download_response(**response_kwargs) + return self.download_response(*response_args, **response_kwargs) class BaseDownloadView(DownloadMixin, View): diff --git a/django_downloadview/views/object.py b/django_downloadview/views/object.py index f590d2c..43f3127 100644 --- a/django_downloadview/views/object.py +++ b/django_downloadview/views/object.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- """Stream files that live in models.""" -from django.views.generic.detail import BaseDetailView +from django.views.generic.detail import SingleObjectMixin -from django_downloadview.views.base import DownloadMixin +from django_downloadview.views.base import BaseDownloadView -class ObjectDownloadView(DownloadMixin, BaseDetailView): +class ObjectDownloadView(SingleObjectMixin, BaseDownloadView): """Serve file fields from models. This class extends BaseDetailView, so you can use its arguments to target @@ -76,3 +76,7 @@ class ObjectDownloadView(DownloadMixin, BaseDetailView): if model_field: basename = getattr(self.object, model_field) return basename + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + return super(ObjectDownloadView, self).get(request, *args, **kwargs) diff --git a/docs/testing.txt b/docs/testing.txt index 7aebe3a..5be8a2b 100644 --- a/docs/testing.txt +++ b/docs/testing.txt @@ -4,18 +4,12 @@ Write tests `django_downloadview` embeds test utilities: -* :func:`~django_downloadview.test.assert_download_response` * :func:`~django_downloadview.test.temporary_media_root` +* :func:`~django_downloadview.test.assert_download_response` +* :func:`~django_downloadview.test.setup_view` * :func:`~django_downloadview.nginx.tests.assert_x_accel_redirect` -************************ -assert_download_response -************************ - -.. autofunction:: django_downloadview.test.assert_download_response - - ******************** temporary_media_root ******************** @@ -23,11 +17,28 @@ temporary_media_root .. autofunction:: django_downloadview.test.temporary_media_root -******* -Example -******* +************************ +assert_download_response +************************ -Here are the tests related to :doc:`StorageDownloadView demo `: +.. autofunction:: django_downloadview.test.assert_download_response + +Examples, related to :doc:`StorageDownloadView demo `: .. literalinclude:: /../demo/demoproject/storage/tests.py :language: python + :lines: 3-7, 9-57 + + +********** +setup_view +********** + +.. autofunction:: django_downloadview.test.setup_view + +Example, related to :doc:`StorageDownloadView demo `: + +.. literalinclude:: /../demo/demoproject/storage/tests.py + :language: python + :lines: 1-2, 8-12, 59- + diff --git a/etc/buildout.cfg b/etc/buildout.cfg index 52d19e6..1281e0a 100644 --- a/etc/buildout.cfg +++ b/etc/buildout.cfg @@ -70,12 +70,12 @@ collective.recipe.omelette = 0.16 coverage = 3.6 distribute = 0.6.34 Django = 1.5 -django-nose = 1.1 +django-nose = 1.2 docutils = 0.10 evg.recipe.activate = 0.5 Jinja2 = 2.6 mock = 1.0.1 -nose = 1.2.1 +nose = 1.3.0 Pygments = 1.6 python-termstyle = 0.1.10 rednose = 0.3 diff --git a/etc/nose.cfg b/etc/nose/base.cfg similarity index 74% rename from etc/nose.cfg rename to etc/nose/base.cfg index b43ad26..8165c25 100644 --- a/etc/nose.cfg +++ b/etc/nose/base.cfg @@ -5,3 +5,5 @@ with-doctest = True rednose = True no-path-adjustment = True all-modules = True +cover-inclusive = True +cover-tests = True diff --git a/etc/nose/demoproject.cfg b/etc/nose/demoproject.cfg new file mode 100644 index 0000000..d77cfcf --- /dev/null +++ b/etc/nose/demoproject.cfg @@ -0,0 +1,4 @@ +[nosetests] +with-coverage = True +cover-package = demoproject +tests = demoproject diff --git a/etc/nose/django_downloadview.cfg b/etc/nose/django_downloadview.cfg new file mode 100644 index 0000000..70e3c08 --- /dev/null +++ b/etc/nose/django_downloadview.cfg @@ -0,0 +1,4 @@ +[nosetests] +with-coverage = True +cover-package = django_downloadview +tests = django_downloadview,tests