From 2df01dcce02c27069e8d75be3cea2d464b1aa16c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Sat, 22 Sep 2018 13:58:39 +0200 Subject: [PATCH] misc: add support for thumbnailing PDF files (#26632) --- debian/control | 3 ++- wcs/fields.py | 4 ++-- wcs/forms/common.py | 13 ++++++------- wcs/forms/root.py | 6 +++--- wcs/qommon/form.py | 15 ++++++++++++--- wcs/qommon/misc.py | 29 ++++++++++++++++++++++++----- wcs/qommon/sessions.py | 8 ++++++++ 7 files changed, 57 insertions(+), 21 deletions(-) diff --git a/debian/control b/debian/control index 395eccc9a..7ce48331e 100644 --- a/debian/control +++ b/debian/control @@ -25,7 +25,8 @@ Recommends: python-dns, python-qrcode, libjs-leaflet, python-magic, - python-docutils + python-docutils, + graphicsmagick Suggests: python-libxml2, python-lasso, python-psycopg2 diff --git a/wcs/fields.py b/wcs/fields.py index 1b33451b0..b23f0e656 100644 --- a/wcs/fields.py +++ b/wcs/fields.py @@ -33,7 +33,7 @@ from django.utils.formats import date_format as django_date_format from qommon import _ from qommon import evalutils from qommon.form import * -from qommon.misc import localstrftime, strftime, date_format, ellipsize +from qommon.misc import localstrftime, strftime, date_format, ellipsize, can_thumbnail from qommon.template import Template, TemplateError from qommon import get_cfg, get_logger @@ -907,7 +907,7 @@ class FileField(WidgetField): t = TemplateIO(html=True) t += htmltext('
') t += htmltext('') % (value.base_filename, self.id) - if value.content_type and value.content_type.startswith('image/'): + if can_thumbnail(value.content_type): t += htmltext('') % self.id t += htmltext('%s') % value t += htmltext('
') diff --git a/wcs/forms/common.py b/wcs/forms/common.py index 726c630ff..add8cf06c 100644 --- a/wcs/forms/common.py +++ b/wcs/forms/common.py @@ -81,17 +81,16 @@ class FileDirectory(Directory): response.set_header( 'content-disposition', 'attachment; filename="%s"' % file.base_filename) - fp = file.get_file_pointer() - if self.thumbnails and file.content_type.startswith('image/'): + if self.thumbnails and misc.can_thumbnail(file.content_type): try: - thumbnail = misc.get_thumbnail(fp) - except misc.ThumbnailError: - pass - else: + thumbnail = misc.get_thumbnail(file.get_filename(), + content_type=file.content_type) response.set_content_type('image/png') return thumbnail + except misc.ThumbnailError: + pass - return fp.read() + return file.get_file_pointer().read() class FilesDirectory(Directory): diff --git a/wcs/forms/root.py b/wcs/forms/root.py index 93ea3541a..d9560b4ba 100644 --- a/wcs/forms/root.py +++ b/wcs/forms/root.py @@ -1114,16 +1114,16 @@ class FormPage(Directory, FormTemplateMixin): if tempfile['charset']: response.set_charset(tempfile['charset']) - file_pointer = get_session().get_tempfile_content(t).get_file_pointer() if get_request().form.get('thumbnail') == '1': try: - thumbnail = misc.get_thumbnail(file_pointer) + thumbnail = misc.get_thumbnail(get_session().get_tempfile_path(t), + content_type=tempfile['content_type']) except misc.ThumbnailError: pass else: response.set_content_type('image/png') return thumbnail - return file_pointer.read() + return get_session().get_tempfile_content(t).get_file_pointer().read() def validating(self, data): self.html_top(self.formdef.name) diff --git a/wcs/qommon/form.py b/wcs/qommon/form.py index b3d3034a3..aa4e503e3 100644 --- a/wcs/qommon/form.py +++ b/wcs/qommon/form.py @@ -71,7 +71,7 @@ from wcs.conditions import Condition, ValidationError from qommon import _, ngettext import misc from .humantime import humanduration2seconds, seconds2humanduration, timewords -from .misc import strftime, C_ +from .misc import strftime, C_, HAS_GM from publisher import get_cfg from .template_utils import render_block_to_string @@ -696,8 +696,11 @@ class FileWithPreviewWidget(CompositeWidget): if not temp: return False - filetype = mimetypes.guess_type(temp.get('orig_filename', '')) - if not (filetype and filetype[0] and filetype[0].startswith('image')): + filetype = (mimetypes.guess_type(temp.get('orig_filename', '')) or [''])[0] + if filetype == 'application/pdf': + return HAS_GM + + if not filetype.startswith('image/'): return False if Image: @@ -819,6 +822,12 @@ class PicklableUpload(Upload): # quack like UploadedFile return self.get_file_pointer() + def get_filename(self): + if not hasattr(self, 'qfilename'): + raise AttributeError('filename') + basedir = os.path.join(get_publisher().app_dir, 'uploads') + return os.path.join(basedir, self.qfilename) + def get_content(self): if hasattr(self, 'qfilename'): filename = os.path.join(get_publisher().app_dir, 'uploads', self.qfilename) diff --git a/wcs/qommon/misc.py b/wcs/qommon/misc.py index da4211c47..56d2b8a5b 100644 --- a/wcs/qommon/misc.py +++ b/wcs/qommon/misc.py @@ -51,6 +51,12 @@ from urllib import urlencode, quote from urllib2 import urlparse from cStringIO import StringIO +try: + subprocess.check_call(['which', 'gm'], stdout=open('/dev/null', 'w')) + HAS_GM = True +except subprocess.CalledProcessError: + HAS_GM = False + class ThumbnailError(Exception): pass @@ -553,19 +559,32 @@ def file_digest(content, chunk_size=100000): digest.update(chunk) return digest.hexdigest() -def get_thumbnail(fp): - if Image is None: + +def can_thumbnail(content_type): + if content_type == 'application/pdf' and not (HAS_GM and Image): + return False + if content_type and content_type.startswith('image/'): + return bool(Image is not None) + return False + + +def get_thumbnail(filepath, content_type=None): + if not can_thumbnail(content_type or ''): raise ThumbnailError() + if content_type == 'application/pdf': + fp = StringIO(subprocess.check_output( + ['gm', 'convert', '-geometry', '500x', filepath, 'png:-'])) + else: + fp = open(filepath) + try: image = Image.open(fp) image.thumbnail((500, 300)) image_thumb_fp = StringIO() image.save(image_thumb_fp, "PNG") except IOError: - # failed to create thumbnail; restore file pointer state and raise - # exception. - fp.seek(0) + # failed to create thumbnail. raise ThumbnailError() return image_thumb_fp.getvalue() diff --git a/wcs/qommon/sessions.py b/wcs/qommon/sessions.py index 14f58b562..2b2d7f2ae 100644 --- a/wcs/qommon/sessions.py +++ b/wcs/qommon/sessions.py @@ -303,6 +303,14 @@ class Session(QommonSession, CaptchaSession, StorableObject): return None return misc.json_loads(open(filename).read()) + def get_tempfile_path(self, token): + temp = self.get_tempfile(token) + if not temp: + return None + dirname = os.path.join(get_publisher().app_dir, 'tempfiles') + filename = os.path.join(dirname, temp['unsigned_token']) + return filename + def get_tempfile_content(self, token): temp = self.get_tempfile(token) if not temp: -- 2.19.0