Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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

…hen reading files from remote storages
  • Loading branch information...
commit 29c05165722b413838a3143bdc91ec0e269f6cd5 1 parent 8661b1f
Chi Shang Cheng authored
29  easy_thumbnails/files.py
... ...
@@ -1,4 +1,5 @@
1 1
 from django.core.files.base import File, ContentFile
  2
+from django.core.files.images import get_image_dimensions
2 3
 from django.core.files.storage import get_storage_class, default_storage, \
3 4
     Storage
4 5
 from django.db.models.fields.files import ImageFieldFile, FieldFile
@@ -226,6 +227,32 @@ def open(self, mode=None, *args, **kwargs):
226 227
         else:
227 228
             return super(ThumbnailFile, self).open(mode, *args, **kwargs)
228 229
 
  230
+    def _get_image_dimensions(self):
  231
+        from numbers import Number
  232
+        if not hasattr(self, '_dimensions_cache'):
  233
+            thumbnail = models.Thumbnail.objects.get_file(self.storage, self.name)
  234
+            if thumbnail:
  235
+                width = thumbnail.width
  236
+                height = thumbnail.height
  237
+                # retrieve image dimensions from db if possible
  238
+                if isinstance(width, Number) and isinstance(height, Number):
  239
+                    self._dimensions_cache = (width, height)
  240
+                else:
  241
+                    # open image and get the image dimensions via PIL
  242
+                    close = self.closed
  243
+                    self.open()
  244
+                    dimensions = get_image_dimensions(self, close=close)
  245
+                    self._dimensions_cache = dimensions
  246
+                    # store in db for future use
  247
+                    thumbnail.width = dimensions[0]
  248
+                    thumbnail.height = dimensions[1]
  249
+                    thumbnail.save()
  250
+            else:
  251
+                close = self.closed
  252
+                self.open()
  253
+                self._dimensions_cache = get_image_dimensions(self, close=close)
  254
+        return self._dimensions_cache
  255
+
229 256
 
230 257
 class Thumbnailer(File):
231 258
     """
@@ -380,12 +407,12 @@ def get_thumbnail(self, thumbnail_options, save=True):
380 407
         thumbnail = self.generate_thumbnail(thumbnail_options)
381 408
         if save:
382 409
             save_thumbnail(thumbnail, self.thumbnail_storage)
383  
-            signals.thumbnail_created.send(sender=thumbnail)
384 410
             # Ensure the right thumbnail name is used based on the transparency
385 411
             # of the image.
386 412
             filename = (utils.is_transparent(thumbnail.image) and
387 413
                         transparent_name or opaque_name)
388 414
             self.get_thumbnail_cache(filename, create=True, update=True)
  415
+            signals.thumbnail_created.send(sender=thumbnail)
389 416
 
390 417
         return thumbnail
391 418
 
47  easy_thumbnails/migrations/0016_auto__add_field_thumbnail_width__add_field_thumbnail_height.py
... ...
@@ -0,0 +1,47 @@
  1
+# encoding: utf-8
  2
+import datetime
  3
+from south.db import db
  4
+from south.v2 import SchemaMigration
  5
+from django.db import models
  6
+
  7
+class Migration(SchemaMigration):
  8
+
  9
+    def forwards(self, orm):
  10
+        
  11
+        # Adding field 'Thumbnail.width'
  12
+        db.add_column('easy_thumbnails_thumbnail', 'width', self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True), keep_default=False)
  13
+
  14
+        # Adding field 'Thumbnail.height'
  15
+        db.add_column('easy_thumbnails_thumbnail', 'height', self.gf('django.db.models.fields.PositiveIntegerField')(null=True, blank=True), keep_default=False)
  16
+
  17
+
  18
+    def backwards(self, orm):
  19
+        
  20
+        # Deleting field 'Thumbnail.width'
  21
+        db.delete_column('easy_thumbnails_thumbnail', 'width')
  22
+
  23
+        # Deleting field 'Thumbnail.height'
  24
+        db.delete_column('easy_thumbnails_thumbnail', 'height')
  25
+
  26
+
  27
+    models = {
  28
+        'easy_thumbnails.source': {
  29
+            'Meta': {'unique_together': "(('storage_hash', 'name'),)", 'object_name': 'Source'},
  30
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  31
+            'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
  32
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
  33
+            'storage_hash': ('django.db.models.fields.CharField', [], {'max_length': '40', 'db_index': 'True'})
  34
+        },
  35
+        'easy_thumbnails.thumbnail': {
  36
+            'Meta': {'unique_together': "(('storage_hash', 'name', 'source'),)", 'object_name': 'Thumbnail'},
  37
+            'height': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
  38
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
  39
+            'modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
  40
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
  41
+            'source': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'thumbnails'", 'to': "orm['easy_thumbnails.Source']"}),
  42
+            'storage_hash': ('django.db.models.fields.CharField', [], {'max_length': '40', 'db_index': 'True'}),
  43
+            'width': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
  44
+        }
  45
+    }
  46
+
  47
+    complete_apps = ['easy_thumbnails']
3  easy_thumbnails/models.py
@@ -55,6 +55,9 @@ class Source(File):
55 55
 
56 56
 class Thumbnail(File):
57 57
     source = models.ForeignKey(Source, related_name='thumbnails')
  58
+    # store thumbnail dimensions in db, much faster than reading from remote storage
  59
+    width = models.PositiveIntegerField(blank=True, null=True)
  60
+    height = models.PositiveIntegerField(blank=True, null=True)
58 61
 
59 62
     class Meta:
60 63
         unique_together = (('storage_hash', 'name', 'source'),)
15  easy_thumbnails/signal_handlers.py
@@ -48,3 +48,18 @@ def generate_aliases_global(fieldfile, **kwargs):
48 48
     # Avoids circular import.
49 49
     from easy_thumbnails.files import generate_all_aliases
50 50
     generate_all_aliases(fieldfile, include_global=True)
  51
+
  52
+
  53
+def save_thumbnail_dimensions(sender, **kwargs):
  54
+    """
  55
+    Save thumbnail dimensions to the thumbnail record,
  56
+    when a new thumbnail file has been created.
  57
+    """
  58
+    from easy_thumbnails import models
  59
+    thumbnail = models.Thumbnail.objects.get_file(sender.storage, sender.name)
  60
+    thumbnail.width = sender.width
  61
+    thumbnail.height = sender.height
  62
+    thumbnail.save()
  63
+
  64
+
  65
+signals.thumbnail_created.connect(save_thumbnail_dimensions)

2 notes on commit 29c0516

Chris Beaven

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

Chris Beaven

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
Chris Beaven

Good job on putting this together.

Chi Shang Cheng
Owner

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

Please sign in to comment.
Something went wrong with that request. Please try again.