diff --git a/docs/advanced_topics/images/image_serve_view.rst b/docs/advanced_topics/images/image_serve_view.rst index 79da8c145e..d6dcf87907 100644 --- a/docs/advanced_topics/images/image_serve_view.rst +++ b/docs/advanced_topics/images/image_serve_view.rst @@ -97,3 +97,52 @@ method in your urls configuration: url(r'^images/([^/]*)/(\d*)/([^/]*)/[^/]*$', ServeView.as_view(action='redirect'), name='wagtailimages_serve'), ] + +Integration with django-sendfile +-------------------------------- + +`django-sendfile`_ offloads the job of transferring the image data to the web +server instead of serving it directly from the Django application. This could +greatly reduce server load in situations where your site has many images being +downloaded but you're unable to use a :ref:`caching_proxy` or a CDN. + +.. _django-sendfile: https://github.com/johnsensible/django-sendfile + +You firstly need to install and configure django-sendfile and configure your +web server to use it. If you haven't done this already, please refer to the +`installation docs `_. + +To serve images with django-sendfile, you can use the ``SendFileView`` class. +This view can be used out of the box: + +.. code-block:: python + + from wagtail.wagtailimages.views.serve import SendFileView + + urlpatterns = [ + ... + + url(r'^images/([^/]*)/(\d*)/([^/]*)/[^/]*$', SendFileView.as_view(), name='wagtailimages_serve'), + ] + +You can customise it to override the backend defined in the ``SENDFILE_BACKEND`` +setting: + +.. code-block:: python + + from wagtail.wagtailimages.views.serve import SendFileView + from project.sendfile_backends import MyCustomBackend + + class MySendFileView(SendFileView): + backend = MyCustomBackend + +You can also customise it to serve private files. For example, if the only need +is to be authenticated (e.g. for Django >= 1.9): + +.. code-block:: python + + from django.contrib.auth.mixins import LoginRequiredMixin + from wagtail.wagtailimages.views.serve import SendFileView + + class PrivateSendFileView(LoginRequiredMixin, SendFileView): + raise_exception = True diff --git a/docs/advanced_topics/performance.rst b/docs/advanced_topics/performance.rst index 3424887a30..5e37a7dfa4 100644 --- a/docs/advanced_topics/performance.rst +++ b/docs/advanced_topics/performance.rst @@ -58,6 +58,8 @@ Wagtail is tested on SQLite, and should work on other Django-supported database Public users ~~~~~~~~~~~~ +.. _caching_proxy: + Caching proxy ------------- diff --git a/wagtail/tests/dummy_sendfile_backend.py b/wagtail/tests/dummy_sendfile_backend.py new file mode 100644 index 0000000000..ea98615595 --- /dev/null +++ b/wagtail/tests/dummy_sendfile_backend.py @@ -0,0 +1,10 @@ +from __future__ import absolute_import, unicode_literals + +from django.http import HttpResponse + + +def sendfile(request, filename, **kwargs): + """ + Dummy sendfile backend implementation. + """ + return HttpResponse('Dummy backend response') diff --git a/wagtail/wagtailimages/tests/tests.py b/wagtail/wagtailimages/tests/tests.py index d414a66d20..2255b81b40 100644 --- a/wagtail/wagtailimages/tests/tests.py +++ b/wagtail/wagtailimages/tests/tests.py @@ -1,11 +1,12 @@ from __future__ import absolute_import, unicode_literals import os +import unittest from django import forms, template from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse -from django.test import TestCase +from django.test import TestCase, override_settings from django.utils import six from mock import MagicMock from taggit.forms import TagField, TagWidget @@ -21,6 +22,12 @@ from wagtail.wagtailimages.views.serve import ServeView, generate_signature, ver from .utils import Image, get_test_image_file +try: + import sendfile # noqa + sendfile_mod = True +except: + sendfile_mod = False + class TestImageTag(TestCase): def setUp(self): @@ -354,6 +361,36 @@ class TestFrontendServeView(TestCase): self.assertEqual(response.status_code, 410) +class TestFrontendSendfileView(TestCase): + + def setUp(self): + self.image = Image.objects.create( + title="Test image", + file=get_test_image_file(), + ) + + @override_settings(SENDFILE_BACKEND='sendfile.backends.development') + @unittest.skipIf(not sendfile_mod, 'Missing django-sendfile app.') + def test_sendfile_nobackend(self): + signature = generate_signature(self.image.id, 'fill-800x600') + response = self.client.get(reverse('wagtailimages_sendfile', + args=(signature, self.image.id, + 'fill-800x600'))) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response['Content-Type'], 'image/png') + + @override_settings(SENDFILE_BACKEND='sendfile.backends.development') + def test_sendfile_dummy_backend(self): + signature = generate_signature(self.image.id, 'fill-800x600') + response = self.client.get(reverse('wagtailimages_sendfile_dummy', + args=(signature, self.image.id, + 'fill-800x600'))) + + self.assertEqual(response.status_code, 200) + self.assertTrue(response.content, 'Dummy backend response') + + class TestRect(TestCase): def test_init(self): rect = Rect(100, 150, 200, 250) diff --git a/wagtail/wagtailimages/tests/urls.py b/wagtail/wagtailimages/tests/urls.py index 510eb3faef..00b17b6bcc 100644 --- a/wagtail/wagtailimages/tests/urls.py +++ b/wagtail/wagtailimages/tests/urls.py @@ -2,10 +2,13 @@ from __future__ import absolute_import, unicode_literals from django.conf.urls import url -from wagtail.wagtailimages.views.serve import ServeView +from wagtail.tests import dummy_sendfile_backend +from wagtail.wagtailimages.views.serve import SendFileView, ServeView urlpatterns = [ url(r'^actions/serve/(.*)/(\d*)/(.*)/[^/]*', ServeView.as_view(action='serve'), name='wagtailimages_serve_action_serve'), url(r'^actions/redirect/(.*)/(\d*)/(.*)/[^/]*', ServeView.as_view(action='redirect'), name='wagtailimages_serve_action_redirect'), url(r'^custom_key/(.*)/(\d*)/(.*)/[^/]*', ServeView.as_view(key='custom'), name='wagtailimages_serve_custom_key'), + url(r'^sendfile/(.*)/(\d*)/(.*)/[^/]*', SendFileView.as_view(), name='wagtailimages_sendfile'), + url(r'^sendfile-dummy/(.*)/(\d*)/(.*)/[^/]*', SendFileView.as_view(backend=dummy_sendfile_backend.sendfile), name='wagtailimages_sendfile_dummy'), ] diff --git a/wagtail/wagtailimages/views/serve.py b/wagtail/wagtailimages/views/serve.py index cc865a7b59..c4d0e39aa9 100644 --- a/wagtail/wagtailimages/views/serve.py +++ b/wagtail/wagtailimages/views/serve.py @@ -14,6 +14,7 @@ from django.utils.decorators import classonlymethod from django.utils.six import text_type from django.views.generic import View +from wagtail.utils.sendfile import sendfile from wagtail.wagtailimages.exceptions import InvalidFilterSpecError from wagtail.wagtailimages.models import SourceImageIOError, get_image_model @@ -78,3 +79,10 @@ class ServeView(View): serve = ServeView.as_view() + + +class SendFileView(ServeView): + backend = None + + def serve(self, rendition): + return sendfile(self.request, rendition.file.path, backend=self.backend)