misc: add support for thumbnailing PDF files (#26632)

This commit is contained in:
Frédéric Péters 2018-09-22 13:58:39 +02:00
parent 13383f60c1
commit 45fa82bc3f
7 changed files with 57 additions and 21 deletions

3
debian/control vendored
View File

@ -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

View File

@ -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('<div class="file-field">')
t += htmltext('<a download="%s" href="[download]?f=%s">') % (value.base_filename, self.id)
if value.content_type and value.content_type.startswith('image/'):
if can_thumbnail(value.content_type):
t += htmltext('<img alt="" src="[download]?f=%s&thumbnail=1"/>') % self.id
t += htmltext('<span>%s</span>') % value
t += htmltext('</a></div>')

View File

@ -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):

View File

@ -1123,16 +1123,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)

View File

@ -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
@ -698,8 +698,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:
@ -821,6 +824,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)

View File

@ -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()

View File

@ -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: