From 558bd757a3b9a07b6d2246fb5aca698f5c0f2a33 Mon Sep 17 00:00:00 2001 From: Erik Dykema Date: Mon, 10 Feb 2014 14:29:48 -0500 Subject: [PATCH 1/7] Add non-downloading inline_file_view To be used in documentation for issue #80 --- demo/demoproject/object/views.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/demo/demoproject/object/views.py b/demo/demoproject/object/views.py index d512ed8..e7525b6 100644 --- a/demo/demoproject/object/views.py +++ b/demo/demoproject/object/views.py @@ -16,3 +16,8 @@ another_file_view = ObjectDownloadView.as_view( deserialized_basename_view = ObjectDownloadView.as_view( model=Document, basename_field='basename') + +#: Serve ``file`` attribute of ``Document`` model, inline rather than as attachment +inline_file_view = ObjectDownloadView.as_view( + model=Document, + attachment=False) From 21cb8f6409d1429a26ed7ba4d2abf9ee63dcc949 Mon Sep 17 00:00:00 2001 From: Erik Dykema Date: Mon, 10 Feb 2014 14:30:41 -0500 Subject: [PATCH 2/7] Add documentation for serving a file inline. Responsive to issue #80 --- docs/views/object.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/views/object.txt b/docs/views/object.txt index 955d960..f78f1ae 100644 --- a/docs/views/object.txt +++ b/docs/views/object.txt @@ -61,6 +61,18 @@ Then here is the code to serve "another_file" instead of the default "file": :language: python :lines: 1-5, 10-12 +*********************************************** +Serving a file inline rather than as a download +*********************************************** + +If you would prefer to serve a file as an element of an exsting page +rather than triggering a download, you can use :attr:`ObjectDownloadView.attachment` to specify +that the HTTP Response object should not have the attachment attribute. + +.. literalinclude:: /../demo/demoproject/object/views.py + :language: python + :lines: 1-5, 20-23 + ********************************** Mapping file attributes to model's ********************************** From 8e83ec559fc2ec561ccc3260dc9698204e8560bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Bryon?= Date: Mon, 3 Mar 2014 07:11:38 +0100 Subject: [PATCH 3/7] Refs #80 - Changed wording in 'serve file inline' documentation. --- demo/demoproject/object/views.py | 2 +- docs/views/object.txt | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/demo/demoproject/object/views.py b/demo/demoproject/object/views.py index e7525b6..2675a6a 100644 --- a/demo/demoproject/object/views.py +++ b/demo/demoproject/object/views.py @@ -17,7 +17,7 @@ deserialized_basename_view = ObjectDownloadView.as_view( model=Document, basename_field='basename') -#: Serve ``file`` attribute of ``Document`` model, inline rather than as attachment +#: Serve ``file`` attribute of ``Document`` model, inline (not as attachment). inline_file_view = ObjectDownloadView.as_view( model=Document, attachment=False) diff --git a/docs/views/object.txt b/docs/views/object.txt index f78f1ae..0b71cbe 100644 --- a/docs/views/object.txt +++ b/docs/views/object.txt @@ -61,18 +61,30 @@ Then here is the code to serve "another_file" instead of the default "file": :language: python :lines: 1-5, 10-12 + *********************************************** -Serving a file inline rather than as a download +Serving a file inline rather than as attachment *********************************************** -If you would prefer to serve a file as an element of an exsting page -rather than triggering a download, you can use :attr:`ObjectDownloadView.attachment` to specify -that the HTTP Response object should not have the attachment attribute. +You can setup :attr:`~django_downloadview.views.base.DownloadMixin.attachment` +to make a view serve a file inline rather than as attachment: + +* ``attachment`` is ``False``: inline file, which content is displayed inside + the browser, as if it was an element of the current page. + +* ``attachment`` is ``True`` (default): attached file, which usually triggers a + download, i.e. the user is prompted to "save the file as ...". .. literalinclude:: /../demo/demoproject/object/views.py :language: python :lines: 1-5, 20-23 +.. note:: + + The actual behaviour client-side depends on browsers and their + configuration. + + ********************************** Mapping file attributes to model's ********************************** From 7875999fe222b1843865dee0d41ebe2f072c563d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Bryon?= Date: Mon, 3 Mar 2014 07:49:30 +0100 Subject: [PATCH 4/7] Refs #80 - Improved documentation about 'DownnloadMixin.attachment' attribute. --- django_downloadview/response.py | 61 ++++++++++++++----------------- django_downloadview/views/base.py | 15 ++++++++ docs/views/custom.txt | 17 +++++++++ docs/views/object.txt | 23 ------------ 4 files changed, 59 insertions(+), 57 deletions(-) diff --git a/django_downloadview/response.py b/django_downloadview/response.py index c22775f..efa9c1f 100644 --- a/django_downloadview/response.py +++ b/django_downloadview/response.py @@ -91,39 +91,7 @@ class DownloadResponse(StreamingHttpResponse): where :attr:`~django.http.StreamingHttpResponse.streaming_content` is a file wrapper. - Constructor differs a bit from :class:`~django.http.response.HttpResponse`: - - ``file_instance`` - A :doc:`file wrapper instance `, such as - :class:`~django.core.files.base.File`. - - ``attachement`` - Boolean. Whether to return the file as attachment or not. - Affects ``Content-Disposition`` header. - - ``basename`` - Unicode. Client-side name of the file to stream. - Only used if ``attachment`` is ``True``. - Affects ``Content-Disposition`` header. - - ``status`` - HTTP status code. - - ``content_type`` - Value for ``Content-Type`` header. - If ``None``, then mime-type and encoding will be populated by the - response (default implementation uses mimetypes, based on file - name). - - ``file_mimetype`` - Value for file's mimetype. If ``None`` (the default), then the file's - mimetype will be guessed via Python's :mod:`mimetypes`. See - :meth:`get_mime_type`. - - ``file_encoding`` - Value for file's encoding. If ``None`` (the default), then the file's - encoding will be guessed via Python's :mod:`mimetypes`. See - :meth:`get_encoding`. + Constructor differs a bit from :class:`~django.http.response.HttpResponse`. Here are some highlights to understand internal mechanisms and motivations: @@ -152,17 +120,42 @@ class DownloadResponse(StreamingHttpResponse): def __init__(self, file_instance, attachment=True, basename=None, status=200, content_type=None, file_mimetype=None, file_encoding=None): - """Constructor.""" + """Constructor. + + :param content_type: Value for ``Content-Type`` header. + If ``None``, then mime-type and encoding will be + populated by the response (default implementation + uses :mod:`mimetypes`, based on file name). + + """ + #: 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) + + #: Client-side name of the file to stream. + #: Only used if ``attachment`` is ``True``. + #: Affects ``Content-Disposition`` header. self.basename = basename + + #: Whether to return the file as attachment or not. + #: Affects ``Content-Disposition`` header. self.attachment = attachment if not content_type: 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 + #: via Python's :mod:`mimetypes`. See :meth:`get_mime_type`. self.file_mimetype = file_mimetype + + #: Value for file's encoding. If ``None`` (the default), then the + #: file's encoding will be guessed via Python's :mod:`mimetypes`. See + #: :meth:`get_encoding`. self.file_encoding = file_encoding + # Apply default headers. for header, value in self.default_headers.items(): if not header in self: diff --git a/django_downloadview/views/base.py b/django_downloadview/views/base.py index 58fb04f..a8b9c82 100644 --- a/django_downloadview/views/base.py +++ b/django_downloadview/views/base.py @@ -28,6 +28,21 @@ class DownloadMixin(object): response_class = DownloadResponse #: Whether to return the response as attachment or not. + #: + #: When ``True`` (the default), the view returns file "as attachment", + #: which usually triggers a "Save the file as ..." prompt. + #: + #: When ``False``, the view returns file "inline", as if it was an element + #: of the current page. + #: + #: .. note:: + #: + #: The actual behaviour client-side depends on the browser and its + #: configuration. + #: + #: In fact, affects the "Content-Disposition" header via :attr:`response's + #: attachment attribute + #: `. attachment = True #: Client-side filename, if only file is returned as attachment. diff --git a/docs/views/custom.txt b/docs/views/custom.txt index 5ecda28..65eba35 100644 --- a/docs/views/custom.txt +++ b/docs/views/custom.txt @@ -4,6 +4,7 @@ Make your own view .. currentmodule:: django_downloadview.views.base + ************* DownloadMixin ************* @@ -41,6 +42,22 @@ The only thing it does is to implement :member-order: bysource +*********************************************** +Serving a file inline rather than as attachment +*********************************************** + +Use :attr:`~DownloadMixin.attachment` to make a view serve a file inline rather +than as attachment, i.e. to display the file as if it was an internal part of a +page rather than triggering "Save file as..." prompt. + +See details in :attr:`attachment API documentation +<~DownloadMixin.attachment>`. + +.. literalinclude:: /../demo/demoproject/object/views.py + :language: python + :lines: 1-5, 20-23 + + ************************************ Handling http not modified responses ************************************ diff --git a/docs/views/object.txt b/docs/views/object.txt index 0b71cbe..4cc3752 100644 --- a/docs/views/object.txt +++ b/docs/views/object.txt @@ -62,29 +62,6 @@ Then here is the code to serve "another_file" instead of the default "file": :lines: 1-5, 10-12 -*********************************************** -Serving a file inline rather than as attachment -*********************************************** - -You can setup :attr:`~django_downloadview.views.base.DownloadMixin.attachment` -to make a view serve a file inline rather than as attachment: - -* ``attachment`` is ``False``: inline file, which content is displayed inside - the browser, as if it was an element of the current page. - -* ``attachment`` is ``True`` (default): attached file, which usually triggers a - download, i.e. the user is prompted to "save the file as ...". - -.. literalinclude:: /../demo/demoproject/object/views.py - :language: python - :lines: 1-5, 20-23 - -.. note:: - - The actual behaviour client-side depends on browsers and their - configuration. - - ********************************** Mapping file attributes to model's ********************************** From ffef9ce7035bc5341eecc2991613218751d6b9f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Bryon?= Date: Mon, 3 Mar 2014 07:58:43 +0100 Subject: [PATCH 5/7] Refs #80 - Referenced options inherited from DownloadMixin in each view's documentation. --- docs/views/http.txt | 10 ++++++++++ docs/views/object.txt | 10 ++++++++++ docs/views/path.txt | 10 ++++++++++ docs/views/storage.txt | 10 ++++++++++ docs/views/virtual.txt | 10 ++++++++++ 5 files changed, 50 insertions(+) diff --git a/docs/views/http.txt b/docs/views/http.txt index 4d334dc..7c785bf 100644 --- a/docs/views/http.txt +++ b/docs/views/http.txt @@ -26,6 +26,16 @@ Setup a view to stream files given URL: :language: python +************ +Base options +************ + +:class:`HTTPDownloadView` inherits from +:class:`~django_downloadview.views.base.DownloadMixin`, which has various +options such as :attr:`~django_downloadview.views.base.DownloadMixin.basename` +or :attr:`~django_downloadview.views.base.DownloadMixin.attachment`. + + ************* API reference ************* diff --git a/docs/views/object.txt b/docs/views/object.txt index 4cc3752..2ed45ce 100644 --- a/docs/views/object.txt +++ b/docs/views/object.txt @@ -41,6 +41,16 @@ Setup a view to stream the ``file`` attribute: :lines: 1-7, 8-10, 17 +************ +Base options +************ + +:class:`ObjectDownloadView` inherits from +:class:`~django_downloadview.views.base.DownloadMixin`, which has various +options such as :attr:`~django_downloadview.views.base.DownloadMixin.basename` +or :attr:`~django_downloadview.views.base.DownloadMixin.attachment`. + + *************************** Serving specific file field *************************** diff --git a/docs/views/path.txt b/docs/views/path.txt index f7d177f..13b336c 100644 --- a/docs/views/path.txt +++ b/docs/views/path.txt @@ -28,6 +28,16 @@ Setup a view to stream files given path: :emphasize-lines: 14 +************ +Base options +************ + +:class:`PathDownloadView` inherits from +:class:`~django_downloadview.views.base.DownloadMixin`, which has various +options such as :attr:`~django_downloadview.views.base.DownloadMixin.basename` +or :attr:`~django_downloadview.views.base.DownloadMixin.attachment`. + + ************************** Computing path dynamically ************************** diff --git a/docs/views/storage.txt b/docs/views/storage.txt index 9878d88..b5d2c81 100644 --- a/docs/views/storage.txt +++ b/docs/views/storage.txt @@ -34,6 +34,16 @@ via URLconfs: :lines: 1-7, 8-10, 14 +************ +Base options +************ + +:class:`StorageDownloadView` inherits from +:class:`~django_downloadview.views.base.DownloadMixin`, which has various +options such as :attr:`~django_downloadview.views.base.DownloadMixin.basename` +or :attr:`~django_downloadview.views.base.DownloadMixin.attachment`. + + ************************** Computing path dynamically ************************** diff --git a/docs/views/virtual.txt b/docs/views/virtual.txt index 706d6af..d7ea396 100644 --- a/docs/views/virtual.txt +++ b/docs/views/virtual.txt @@ -18,6 +18,16 @@ it returns a suitable file wrapper... exited. +************ +Base options +************ + +:class:`VirtualDownloadView` inherits from +:class:`~django_downloadview.views.base.DownloadMixin`, which has various +options such as :attr:`~django_downloadview.views.base.DownloadMixin.basename` +or :attr:`~django_downloadview.views.base.DownloadMixin.attachment`. + + *************************************** Serve text (string or unicode) or bytes *************************************** From eeaabd2a37bf6bf4614bdbcac3d638bec8796e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Bryon?= Date: Mon, 3 Mar 2014 08:15:46 +0100 Subject: [PATCH 6/7] Refs #80 - Added a test around demo's 'object:inline_file' URL. --- demo/demoproject/object/tests.py | 14 ++++++++++++++ demo/demoproject/object/urls.py | 3 +++ django_downloadview/test.py | 9 +++++++-- docs/views/object.txt | 2 +- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/demo/demoproject/object/tests.py b/demo/demoproject/object/tests.py index 4b6b537..527936a 100644 --- a/demo/demoproject/object/tests.py +++ b/demo/demoproject/object/tests.py @@ -68,3 +68,17 @@ class DeserializedBasenameTestCase(django.test.TestCase): content=file_content, basename=basename, mime_type='text/plain') + + +class InlineFileTestCase(django.test.TestCase): + @temporary_media_root() + def test_download_response(self): + "'inline_file_view' streams Document.file inline." + setup_document() + 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) diff --git a/demo/demoproject/object/urls.py b/demo/demoproject/object/urls.py index 1e16d08..a231fa1 100644 --- a/demo/demoproject/object/urls.py +++ b/demo/demoproject/object/urls.py @@ -14,4 +14,7 @@ urlpatterns = patterns( 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_-]+)/$', + views.inline_file_view, + name='inline_file'), ) diff --git a/django_downloadview/test.py b/django_downloadview/test.py index 714e8c2..ef0ba00 100644 --- a/django_downloadview/test.py +++ b/django_downloadview/test.py @@ -144,8 +144,13 @@ class DownloadResponseValidator(object): value) def assert_attachment(self, test_case, response, value): - test_case.assertEqual('attachment;' in response['Content-Disposition'], - value) + if value: + test_case.assertTrue( + 'attachment;' in response['Content-Disposition']) + else: + test_case.assertTrue( + 'Content-Disposition' not in response + or 'attachment;' not in response['Content-Disposition']) def assert_download_response(test_case, response, **assertions): diff --git a/docs/views/object.txt b/docs/views/object.txt index 2ed45ce..db49996 100644 --- a/docs/views/object.txt +++ b/docs/views/object.txt @@ -38,7 +38,7 @@ Setup a view to stream the ``file`` attribute: .. literalinclude:: /../demo/demoproject/object/urls.py :language: python - :lines: 1-7, 8-10, 17 + :lines: 1-7, 8-10, 20 ************ From d29e3532e8f4c73edd93c9c39109559d7464a595 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Bryon?= Date: Mon, 3 Mar 2014 08:19:43 +0100 Subject: [PATCH 7/7] Refs #80 - Updated changelog. --- CHANGELOG | 4 ++++ docs/Makefile | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9a74610..1b1b1d2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,10 @@ future releases, check `milestones`_ and :doc:`/about/vision`. - Feature #46: introduced support for Python>=3.3. +- Feature #80: added documentation about "how to serve a file inline VS how to + serve a file as attachment". Improved documentation of views' base options + inherited from ``DownloadMixin``. + - Feature #74: the Makefile in project's repository no longer creates a virtualenv. Developers setup the environment as they like, i.e. using virtualenv, virtualenvwrapper or whatever. Tests are run with tox. diff --git a/docs/Makefile b/docs/Makefile index f059983..50f203f 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,7 +3,7 @@ # You can set these variables from the command line. SPHINXOPTS = -SPHINXBUILD = sphinx-build -W # Turn warnings into errors. +SPHINXBUILD = sphinx-build # Turn warnings into errors. PAPER = BUILDDIR = ../var/docs