Skip to content

Commit

Permalink
Pre generate thumbnails (#91)
Browse files Browse the repository at this point in the history
* pregenerated thumbnail sizes

* fix test

* refactor

* update readme file
  • Loading branch information
marsha97 authored Nov 30, 2020
1 parent dd672b9 commit bf0a80a
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 5 deletions.
20 changes: 20 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,26 @@ And here's how you'd use it in Django's template:

{{ food.image.thumbnails.small.url }} # Returns "small" sized thumbnail URL

Use `resize_source_to` to resize your image while saving it:

.. code-block:: python
from thumbnails.fields import ImageField
class Food(models.Model):
image = ImageField(resize_source_to="medium")
Assuming `medium` is the size that you define in the `settings`.
By passing `medium` your saved image will be resized into `medium`'s size

Use `pregenerated_sizes` to save your thumbnails into storage backend while saving it:

.. code-block:: python
from thumbnails.fields import ImageField
class Food(models.Model):
image = ImageField(pregenerated_sizes=["small", "large", "medium")
`django-thumbnails` comes with a few builtin image processors::
Expand Down
16 changes: 14 additions & 2 deletions thumbnails/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .backends import metadata, storage
from .backends.metadata import ImageMeta
from .files import ThumbnailedImageFile
from .images import Thumbnail
from .images import Thumbnail, save
from . import processors, post_processors, conf


Expand All @@ -17,10 +17,12 @@ class ImageField(DjangoImageField):

def __init__(self, *args, **kwargs):
self.resize_source_to = kwargs.pop('resize_source_to', None)
self.pregenerated_sizes = kwargs.pop('pregenerated_sizes', [])
if kwargs.get('storage'):
raise ValueError('Please define storage backend in settings.py instead on the field itself')
kwargs['storage'] = storage.get_backend()
self.storage_backend = storage.get_backend()
self.metadata_backend = metadata.get_backend()
kwargs['storage'] = self.storage_backend
super(ImageField, self).__init__(*args, **kwargs)

def deconstruct(self):
Expand Down Expand Up @@ -52,6 +54,16 @@ def pre_save(self, model_instance, add):

filename = str(shortuuid.uuid()) + file_type
file.save(filename, image_file, save=False)

for size in self.pregenerated_sizes:
if size == self.resize_source_to:
# no need to process file if it is in resize_source_to,
# since it had been processed right before this
save(file.name, size, self.metadata_backend,
self.storage_backend, image_file)
continue
file.thumbnails.create(size)

return file

def south_field_triple(self):
Expand Down
7 changes: 6 additions & 1 deletion thumbnails/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,13 @@ def create(source_name, size, metadata_backend=None, storage_backend=None):

thumbnail_file = processors.process(storage_backend.open(source_name), size)
thumbnail_file = post_processors.process(thumbnail_file, size)

return save(source_name, size, metadata_backend, storage_backend, thumbnail_file)


def save(source_name, size, metadata_backend, storage_backend, image_file):
name = get_thumbnail_name(source_name, size)
name = storage_backend.save(name, thumbnail_file)
name = storage_backend.save(name, image_file)

metadata = metadata_backend.add_thumbnail(source_name, size, name)
return Thumbnail(metadata=metadata, storage=storage_backend)
Expand Down
6 changes: 6 additions & 0 deletions thumbnails/tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ class TestModel(models.Model):
avatar = ImageField(upload_to='avatars', resize_source_to='source')
profile_picture = ImageField(upload_to='avatars', blank=True, null=True, resize_source_to='source')
card_identity_picture = ImageField(upload_to='identity_card', blank=True, null=True, resize_source_to='source_with_format')


class TestPregeneratedSizesModel(models.Model):
logo = ImageField(upload_to='logos', blank=True, null=True, pregenerated_sizes=['small', 'large'])
photo = ImageField(upload_to='photos', blank=True, null=True, resize_source_to='source_with_format',
pregenerated_sizes=['source_with_format', 'default', 'large'])
50 changes: 48 additions & 2 deletions thumbnails/tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from thumbnails.fields import fetch_thumbnails
from thumbnails.files import Thumbnail, FallbackImage

from .models import TestModel
from .models import TestModel, TestPregeneratedSizesModel


class ImageFieldTest(TestCase):
Expand Down Expand Up @@ -37,6 +37,8 @@ def setUp(self):

def tearDown(self):
self.instance.avatar.storage.delete_temporary_storage()
self.instance.card_identity_picture.storage.delete_temporary_storage()

super(ImageFieldTest, self).tearDown()

def test_image_field(self):
Expand All @@ -56,7 +58,7 @@ def test_image_field(self):
self.instance.avatar.thumbnails.delete(size='small')
self.assertFalse(os.path.isfile(os.path.join(self.avatar_folder, self.filename + '_small' + self.ext)))

# Test convert png image to webp image, ImageField with resize
# Test convert png image to webp image, ImageField with resize and pregenerated sizes
self.assertEqual(self.identity_ext, '.webp')

# After convert to webp, make sure resize can be running as normal
Expand Down Expand Up @@ -341,3 +343,47 @@ def test_populate_redis_backend_with_size(self):
self.assertEqual(thumbnails._thumbnails[size].name,
thumbnails.get(size).name)
self.assertEqual(set(sizes), set(['small', 'large', 'source_with_format']))


class PregeneratedFilesTest(TestCase):
def setUp(self):
self.instance = TestPregeneratedSizesModel.objects.create()

def tearDown(self):
if self.instance.logo:
self.instance.logo.storage.delete_temporary_storage()
if self.instance.photo:
self.instance.photo.storage.delete_temporary_storage()

super(PregeneratedFilesTest, self).tearDown()

def test_image_field(self):
# Test for thumbanils with pregenerated sizes
with open('thumbnails/tests/tests.png', 'rb') as image_file:
self.instance.logo = File(image_file)
self.instance.save()

logo_folder = \
os.path.join(self.instance.logo.storage.temporary_location, conf.BASE_DIR, 'logos')
logo_basename = os.path.basename(self.instance.logo.path)
logo_filename, logo_ext = os.path.splitext(logo_basename)

pregenerated_files = os.listdir(logo_folder)
self.assertEqual(len(pregenerated_files), 2)
for size in ['_large', '_small']:
self.assertIn(logo_filename + size + logo_ext, pregenerated_files)

# Test for thumbnails with resource to and pregenerated files
with open('thumbnails/tests/tests.png', 'rb') as image_file:
self.instance.photo = File(image_file)
self.instance.save()

photo_folder = \
os.path.join(self.instance.photo.storage.temporary_location, conf.BASE_DIR, 'photos')
photo_basename = os.path.basename(self.instance.photo.path)
photo_filename, photo_ext = os.path.splitext(photo_basename)

pregenerated_files = os.listdir(photo_folder)
self.assertEqual(len(pregenerated_files), 3)
for size in ['_large', '_default', '_source_with_format']:
self.assertIn(photo_filename + size + photo_ext, pregenerated_files)

0 comments on commit bf0a80a

Please sign in to comment.