From 48b598000f5f5bf73a22893572e1cccb3ad66208 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Sat, 31 Mar 2018 11:41:47 +0200 Subject: [PATCH] use sorl-thumbnail (fixes #15857) It replaces GraphicsMagick, we do not use the templatetag as the files are private. --- debian/control | 3 +- fargo/fargo/admin.py | 10 +-- fargo/fargo/management/commands/fargo-cleanup.py | 2 + fargo/fargo/models.py | 80 ++++++++++++++---------- fargo/fargo/views.py | 6 +- fargo/settings.py | 3 + fargo/templates/fargo/table.html | 8 ++- setup.py | 1 + 8 files changed, 64 insertions(+), 49 deletions(-) diff --git a/debian/control b/debian/control index e95090e..48ff339 100644 --- a/debian/control +++ b/debian/control @@ -13,7 +13,8 @@ Depends: ${misc:Depends}, ${python:Depends}, python-django-filters (>= 1), python-django-filters (<< 2), python-gadjo, - python-magic + python-magic, + python-sorl-thumbnail Recommends: python-django-mellon Description: Fargo Document Box (Python module) diff --git a/fargo/fargo/admin.py b/fargo/fargo/admin.py index d7fdaad..08c8f4a 100644 --- a/fargo/fargo/admin.py +++ b/fargo/fargo/admin.py @@ -14,10 +14,7 @@ class UserDocumentAdmin(admin.ModelAdmin): 'origin__label'] def thumbnail(self, instance): - data_url = instance.document.thumbnail_data_url - if data_url: - return format_html('', data_url) - return '' + return instance.document.thumbnail_img_tag thumbnail.short_description = _('thumbnail') @@ -32,10 +29,7 @@ class DocumentAdmin(admin.ModelAdmin): return u', '.join(unicode(u) for u in qs) def thumbnail(self, instance): - data_url = instance.thumbnail_data_url - if data_url: - return format_html('', data_url) - return '' + return instance.thumbnail_img_tag thumbnail.short_description = _('thumbnail') diff --git a/fargo/fargo/management/commands/fargo-cleanup.py b/fargo/fargo/management/commands/fargo-cleanup.py index 9b133ea..1e1c44b 100644 --- a/fargo/fargo/management/commands/fargo-cleanup.py +++ b/fargo/fargo/management/commands/fargo-cleanup.py @@ -1,4 +1,5 @@ from django.core.management.base import BaseCommand +from django.core.management import call_command from fargo.fargo.utils import cleanup @@ -8,4 +9,5 @@ class Command(BaseCommand): def handle(self, *args, **options): cleanup() + call_command('thumbnail', 'cleanup') diff --git a/fargo/fargo/models.py b/fargo/fargo/models.py index 6f0f17b..0f26cdf 100644 --- a/fargo/fargo/models.py +++ b/fargo/fargo/models.py @@ -11,10 +11,14 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ from django.utils.text import slugify from django.utils.http import urlquote +from django.utils.html import format_html from django.dispatch import receiver from django.db.models.signals import post_save, post_delete from django.core.files.storage import default_storage +from sorl.thumbnail import get_thumbnail, delete +from sorl.thumbnail.conf import settings as thumbnail_settings + from jsonfield import JSONField from . import utils, managers @@ -83,10 +87,13 @@ class UserDocument(models.Model): def get_download_url(self): return reverse('download', kwargs={'pk': self.id, 'filename': self.filename_encoded}) - def get_thumbnail_url(self): - if self.document.thumbnail: - return reverse('thumbnail', kwargs={'pk': self.id, 'filename': self.filename_encoded}) - return '' + @property + def thumbnail_image(self): + thumbnail = self.document.thumbnail + if not thumbnail: + return '' + src = reverse('thumbnail', kwargs={'pk': self.id, 'filename': self.filename_encoded}) + return {'src': src, 'width': thumbnail.width, 'height': thumbnail.height} @property def css_classes(self): @@ -182,27 +189,48 @@ class Document(models.Model): self.mime_type = utils.get_mime_type(self.content.file.name) or '' super(Document, self).save(*args, **kwargs) - @property - def thumbnail_path(self): - name = os.path.basename(self.content.name) - return os.path.join('thumbmails', name + '.png') - - @property - def thumbnail_full_path(self): - return default_storage.path(self.thumbnail_path) - @property def thumbnail(self): + if not (self.mime_type.startswith('image/') or self.mime_type == 'application/pdf'): + return None try: - return default_storage.open(self.thumbnail_path) - except IOError: + thumbnail = get_thumbnail(self.content, '200x200') + except: # sorl-thumbnail can crash in unexpected ways return None + try: + # check file exists and is readable + default_storage.open(thumbnail.name) + return thumbnail + except IOError: + pass + return None @property def thumbnail_data_url(self): - if self.thumbnail: - return 'data:image/png;base64,%s' % base64.b64encode(self.thumbnail.read()) - return None + thumbnail = self.thumbnail + if not thumbnail: + return '' + + mime_type = 'image/' + thumbnail_settings.THUMBNAIL_FORMAT.lower() + return 'data:%s;base64,%s' % (mime_type, base64.b64encode(thumbnail.read())) + + @property + def thumbnail_img_tag(self): + thumbnail = self.thumbnail + if not thumbnail: + return '' + + return format_html('', + thumbnail.width, + thumbnail.height, + self.thumbnail_data_url) + + @property + def thumbnail_image(self): + thumbnail = self.thumbnail + if not thumbnail: + return '' + return {'src': self.thumbnail_data_url, 'width': thumbnail.width, 'height': thumbnail.height} class Meta: verbose_name = _('document') @@ -210,23 +238,9 @@ class Document(models.Model): ordering = ('content_hash',) -@receiver(post_save, sender=Document) -def create_thumbnail(sender, instance, created, **kwargs): - if not created: - return - - def do(): - dirpath = os.path.dirname(instance.thumbnail_full_path) - if not os.path.isdir(dirpath): - os.makedirs(dirpath) - subprocess.call(['gm', 'convert', '-geometry', 'x200', - instance.content.file.name, - instance.thumbnail_full_path]) - threading.Thread(target=do).start() - - @receiver(post_delete, sender=Document) def delete_file(sender, instance, **kwargs): if instance.content: if os.path.isfile(instance.content.path): os.remove(instance.content.path) + delete(instance.content) diff --git a/fargo/fargo/views.py b/fargo/fargo/views.py index 03e4f82..904a3c5 100644 --- a/fargo/fargo/views.py +++ b/fargo/fargo/views.py @@ -179,14 +179,10 @@ class Thumbnail(Documents, View): def get(self, request, pk, filename): user_document = get_object_or_404(self.get_queryset(), pk=pk, user=self.request.user) - return self.return_user_document(user_document) - - def return_user_document(self, user_document): thumbnail = user_document.document.thumbnail if not thumbnail: raise Http404 - response = HttpResponse(thumbnail.chunks(), content_type='image/png') - return response + return HttpResponse(thumbnail.chunks(), content_type='image/jpeg') class RemoteDownload(Download): diff --git a/fargo/settings.py b/fargo/settings.py index 22f65f3..9b54b6e 100644 --- a/fargo/settings.py +++ b/fargo/settings.py @@ -41,6 +41,7 @@ INSTALLED_APPS = ( 'fargo.fargo', 'rest_framework', 'fargo.oauth2', + 'sorl.thumbnail', ) MIDDLEWARE_CLASSES = ( @@ -232,6 +233,8 @@ LOGGING = { INCLUDE_EDIT_LINK = False +THUMBNAIL_ENGINE = 'sorl.thumbnail.engines.convert_engine.Engine' + FARGO_CODE_LIFETIME = 300 FARGO_ACCESS_TOKEN_LIFETIME = 3600 FARGO_OAUTH2_TEMPFILE_LIFETIME = 86400 diff --git a/fargo/templates/fargo/table.html b/fargo/templates/fargo/table.html index 1b08d1f..ef72160 100644 --- a/fargo/templates/fargo/table.html +++ b/fargo/templates/fargo/table.html @@ -31,8 +31,12 @@ {% for column, cell in row.items %} {% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %} {% endfor %} - {% with url=row.record.get_thumbnail_url %} - {% if url %}{% endif %} + {% with thumbnail=row.record.thumbnail_image %} + + {% if thumbnail %} + + {% endif %} + {% endwith %} {% block action-column %} diff --git a/setup.py b/setup.py index 345ccb9..e394204 100755 --- a/setup.py +++ b/setup.py @@ -105,6 +105,7 @@ setup( 'djangorestframework>=3.3,<3.4', 'file-magic', 'requests', + 'sorl-thumbnail', ], zip_safe=False, cmdclass={ -- 2.16.3