Allow specifying an alternative storage backend for image renditions

- add setting `WAGTAILIMAGES_RENDITION_STORAGE`
- add AbstractRendition file storage to use new setting
- resolves #3183
pull/8166/head
eevel 2022-03-16 22:00:48 -05:00 zatwierdzone przez LB (Ben Johnston)
rodzic 7d3ee400fe
commit 1c7c5cfc0b
6 zmienionych plików z 78 dodań i 2 usunięć

Wyświetl plik

@ -41,6 +41,7 @@ Changelog
* Add documentation that describes how to use `ModelAdmin` to manage `Tag`s (Abdulmajeed Isa)
* Rename the setting `BASE_URL` (undocumented) to `WAGTAILADMIN_BASE_URL` and add to documentation, `BASE_URL` will be removed in a future release (Sandil Ranasinghe)
* Validate to and from email addresses within form builder pages when using `AbstractEmailForm` (Jake Howard)
* Add `WAGTAILIMAGES_RENDITION_STORAGE` setting to allow an alternative image rendition storage (Heather White)
* Fix: When using `simple_translations` ensure that the user is redirected to the page edit view when submitting for a single locale (Mitchel Cabuloy)
* Fix: When previewing unsaved changes to `Form` pages, ensure that all added fields are correctly shown in the preview (Joshua Munn)
* Fix: When Documents (e.g. PDFs) have been configured to be served inline via `WAGTAILDOCS_CONTENT_TYPES` & `WAGTAILDOCS_INLINE_CONTENT_TYPES` ensure that the filename is correctly set in the `Content-Disposition` header so that saving the files will use the correct filename (John-Scott Atlakson)

Wyświetl plik

@ -586,6 +586,7 @@ Contributors
* Abdulmajeed Isa
* Sandil Ranasinghe
* Caio Jhonny
* Heather White
Translators
===========

Wyświetl plik

@ -335,6 +335,19 @@ Specifies the number of items per page shown when viewing an image's usage (see
Specifies the number of images shown per page in the image chooser modal.
.. _wagtailimages_rendition_storage:
``WAGTAILIMAGES_RENDITION_STORAGE``
-----------------------------------
.. code-block:: python
WAGTAILIMAGES_RENDITION_STORAGE = 'myapp.backends.MyCustomStorage'
This setting allows image renditions to be stored using an alternative storage backend. The default is ``None``, which will use Django's default `FileSystemStorage`.
Custom storage classes should subclass ``django.core.files.storage.Storage``. See the :doc:`Django file storage API <django:ref/files/storage>`.
Documents
=========

Wyświetl plik

@ -71,6 +71,7 @@ class LandingPage(Page):
* Add documentation that describes how to use `ModelAdmin` to manage `Tag`s (Abdulmajeed Isa)
* Rename the setting `BASE_URL` (undocumented) to [`WAGTAILADMIN_BASE_URL`](wagtailadmin_base_url) and add to documentation, `BASE_URL` will be removed in a future release (Sandil Ranasinghe)
* Validate to and from email addresses within form builder pages when using `AbstractEmailForm` (Jake Howard)
* Add [`WAGTAILIMAGES_RENDITION_STORAGE`](wagtailimages_rendition_storage) setting to allow an alternative image rendition storage (Heather White)
### Bug fixes

Wyświetl plik

@ -10,10 +10,12 @@ from django.conf import settings
from django.core import checks
from django.core.cache import InvalidCacheBackendError, caches
from django.core.files import File
from django.core.files.storage import default_storage
from django.db import models
from django.forms.utils import flatatt
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.module_loading import import_string
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from taggit.managers import TaggableManager
@ -70,6 +72,21 @@ def get_rendition_upload_to(instance, filename):
return instance.get_upload_to(filename)
def get_rendition_storage():
"""
Obtain the storage object for an image rendition file.
Returns custom storage (if defined), or the default storage.
This needs to be a module-level function, because we do not yet
have an instance when Django loads the models.
"""
storage = getattr(settings, "WAGTAILIMAGES_RENDITION_STORAGE", default_storage)
if isinstance(storage, str):
module = import_string(storage)
storage = module()
return storage
class ImageFileMixin:
def is_stored_locally(self):
"""
@ -597,7 +614,10 @@ class Filter:
class AbstractRendition(ImageFileMixin, models.Model):
filter_spec = models.CharField(max_length=255, db_index=True)
file = models.ImageField(
upload_to=get_rendition_upload_to, width_field="width", height_field="height"
upload_to=get_rendition_upload_to,
storage=get_rendition_storage,
width_field="width",
height_field="height",
)
width = models.IntegerField(editable=False)
height = models.IntegerField(editable=False)

Wyświetl plik

@ -3,6 +3,7 @@ import unittest
from django.contrib.auth.models import Group, Permission
from django.core.cache import caches
from django.core.files import File
from django.core.files.storage import DefaultStorage, Storage
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db.utils import IntegrityError
from django.test import TestCase
@ -10,7 +11,7 @@ from django.test.utils import override_settings
from django.urls import reverse
from willow.image import Image as WillowImage
from wagtail.images.models import Rendition, SourceImageIOError
from wagtail.images.models import Rendition, SourceImageIOError, get_rendition_storage
from wagtail.images.rect import Rect
from wagtail.models import Collection, GroupCollectionPermission, Page
from wagtail.test.testapp.models import (
@ -23,6 +24,11 @@ from wagtail.test.utils import WagtailTestUtils
from .utils import Image, get_test_image_file
class CustomStorage(Storage):
def __init__(self):
super().__init__()
class TestImage(TestCase):
def setUp(self):
# Create an image for running tests on
@ -346,6 +352,40 @@ class TestRenditions(TestCase):
rendition.background_position_style, "background-position: 50% 50%;"
)
def test_custom_rendition_backend_setting(self):
"""
Test the usage of WAGTAILIMAGES_RENDITION_STORAGE setting.
"""
# when setting is not set, instance.get_storage() returns DefaultStorage
from django.conf import settings
bkp = settings
self.assertFalse(hasattr(settings, "WAGTAILIMAGES_RENDITION_STORAGE"))
rendition1 = self.image.get_rendition("min-120x120")
self.assertIsInstance(rendition1.image.file.storage, DefaultStorage)
# when setting is set to a path
setattr(
settings,
"WAGTAILIMAGES_RENDITION_STORAGE",
"wagtail.images.tests.test_models.CustomStorage",
)
backend = get_rendition_storage()
self.assertIsInstance(backend, CustomStorage)
# when setting is set directly, get_rendition_storage() returns the custom storage backend
class CustomStorage2(Storage):
def __init__(self):
super().__init__()
setattr(settings, "WAGTAILIMAGES_RENDITION_STORAGE", CustomStorage2())
backend = get_rendition_storage()
self.assertIsInstance(backend, CustomStorage2)
# clean up
settings = bkp
class TestUsageCount(TestCase):
fixtures = ["test.json"]