Permalink
Browse files

Store thumbnail dimensions in db to improve performance, especially w…

…hen reading files from remote storages
  • Loading branch information...
1 parent 8661b1f commit 29c05165722b413838a3143bdc91ec0e269f6cd5 Chi Shang Cheng committed Jun 26, 2012
@@ -1,4 +1,5 @@
from django.core.files.base import File, ContentFile
+from django.core.files.images import get_image_dimensions
from django.core.files.storage import get_storage_class, default_storage, \
Storage
from django.db.models.fields.files import ImageFieldFile, FieldFile
@@ -226,6 +227,32 @@ def open(self, mode=None, *args, **kwargs):
else:
return super(ThumbnailFile, self).open(mode, *args, **kwargs)
+ def _get_image_dimensions(self):
+ from numbers import Number
@SmileyChris
SmileyChris Jun 26, 2012

Hrm, do we need to rely on numbers? Django still supports Python 2.5 at the moment.

+ if not hasattr(self, '_dimensions_cache'):
+ thumbnail = models.Thumbnail.objects.get_file(self.storage, self.name)
+ if thumbnail:
+ width = thumbnail.width
+ height = thumbnail.height
+ # retrieve image dimensions from db if possible
+ if isinstance(width, Number) and isinstance(height, Number):
+ self._dimensions_cache = (width, height)
+ else:
+ # open image and get the image dimensions via PIL
+ close = self.closed
+ self.open()
+ dimensions = get_image_dimensions(self, close=close)
+ self._dimensions_cache = dimensions
+ # store in db for future use
+ thumbnail.width = dimensions[0]
+ thumbnail.height = dimensions[1]
+ thumbnail.save()
+ else:
+ close = self.closed
+ self.open()
+ self._dimensions_cache = get_image_dimensions(self, close=close)
@SmileyChris
SmileyChris Jun 26, 2012

Being finicky, but this could be a bit dryer here. Something like

if thumbnail and thumbnail.width is not None and thumbnail.height is not None:
    self._dimensions_cache = (thumbnail.width, thumbnail.height)
else:
    # get dimensions
    if thumbnail:
        # save dimensions
+ return self._dimensions_cache
+
class Thumbnailer(File):
"""
@@ -380,12 +407,12 @@ def get_thumbnail(self, thumbnail_options, save=True):
thumbnail = self.generate_thumbnail(thumbnail_options)
if save:
save_thumbnail(thumbnail, self.thumbnail_storage)
- signals.thumbnail_created.send(sender=thumbnail)
# Ensure the right thumbnail name is used based on the transparency
# of the image.
filename = (utils.is_transparent(thumbnail.image) and
transparent_name or opaque_name)
self.get_thumbnail_cache(filename, create=True, update=True)
+ signals.thumbnail_created.send(sender=thumbnail)
return thumbnail
@@ -0,0 +1,47 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding field 'Thumbnail.width'
+ db.add_column('easy_thumbnails_thumbnail', 'width', self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True), keep_default=False)
+
+ # Adding field 'Thumbnail.height'
+ db.add_column('easy_thumbnails_thumbnail', 'height', self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True), keep_default=False)
+
+
+ def backwards(self, orm):
+
+ # Deleting field 'Thumbnail.width'
+ db.delete_column('easy_thumbnails_thumbnail', 'width')
+
+ # Deleting field 'Thumbnail.height'
+ db.delete_column('easy_thumbnails_thumbnail', 'height')
+
+
+ models = {
+ 'easy_thumbnails.source': {
+ 'Meta': {'unique_together': "(('storage_hash', 'name'),)", 'object_name': 'Source'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'storage_hash': ('django.db.models.fields.CharField', [], {'max_length': '40', 'db_index': 'True'})
+ },
+ 'easy_thumbnails.thumbnail': {
+ 'Meta': {'unique_together': "(('storage_hash', 'name', 'source'),)", 'object_name': 'Thumbnail'},
+ 'height': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'thumbnails'", 'to': "orm['easy_thumbnails.Source']"}),
+ 'storage_hash': ('django.db.models.fields.CharField', [], {'max_length': '40', 'db_index': 'True'}),
+ 'width': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['easy_thumbnails']
@@ -55,6 +55,9 @@ class Source(File):
class Thumbnail(File):
source = models.ForeignKey(Source, related_name='thumbnails')
+ # store thumbnail dimensions in db, much faster than reading from remote storage
+ width = models.PositiveIntegerField(blank=True, null=True)
+ height = models.PositiveIntegerField(blank=True, null=True)
class Meta:
unique_together = (('storage_hash', 'name', 'source'),)
@@ -48,3 +48,18 @@ def generate_aliases_global(fieldfile, **kwargs):
# Avoids circular import.
from easy_thumbnails.files import generate_all_aliases
generate_all_aliases(fieldfile, include_global=True)
+
+
+def save_thumbnail_dimensions(sender, **kwargs):
+ """
+ Save thumbnail dimensions to the thumbnail record,
+ when a new thumbnail file has been created.
+ """
+ from easy_thumbnails import models
+ thumbnail = models.Thumbnail.objects.get_file(sender.storage, sender.name)
+ thumbnail.width = sender.width
+ thumbnail.height = sender.height
+ thumbnail.save()
+
+
+signals.thumbnail_created.connect(save_thumbnail_dimensions)

2 comments on commit 29c0516

@SmileyChris

Good job on putting this together.

@cscheng
Owner

Thanks! I also implemented the changes you suggested in _get_image_dimensions().

Please sign in to comment.