diff --git a/geonode/base/models.py b/geonode/base/models.py index 0915bd78717..fd619e89b07 100644 --- a/geonode/base/models.py +++ b/geonode/base/models.py @@ -1741,8 +1741,7 @@ def save_thumbnail(self, filename, image): url = storage_manager.url(upload_path) try: # Optimize the Thumbnail size and resolution - _default_thumb_size = getattr( - settings, 'THUMBNAIL_GENERATOR_DEFAULT_SIZE', {'width': 240, 'height': 200}) + _default_thumb_size = settings.THUMBNAIL_SIZE im = Image.open(storage_manager.open(actual_name)) im.thumbnail( (_default_thumb_size['width'], _default_thumb_size['height']), diff --git a/geonode/documents/renderers.py b/geonode/documents/renderers.py deleted file mode 100644 index dda0fc3e736..00000000000 --- a/geonode/documents/renderers.py +++ /dev/null @@ -1,118 +0,0 @@ -######################################################################### -# -# Copyright (C) 2017 OSGeo -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -######################################################################### - -import io -import os -import subprocess -import traceback -import tempfile - -from django.conf import settings -from threading import Timer -from mimetypes import guess_type -from urllib.request import pathname2url - - -class ConversionError(Exception): - """Raise when conversion was unsuccessful.""" - pass - - -class MissingPILError(Exception): - """Raise when could not import PIL package.""" - pass - - -def guess_mimetype(document_path): - """Guess mime type for a file in local filesystem. - - Return string containing valid mime type. - """ - document_url = pathname2url(document_path) - return guess_type(document_url)[0] - - -def render_document(document_path, extension="png"): - """Render document using `unconv` converter. - - Package `unoconv` has to be installed and available on system - path. Return `NamedTemporaryFile` instance. - """ - - # workaround: https://github.com/dagwieers/unoconv/issues/167 - # first convert a document to PDF and continue - dispose_input = False - if extension != "pdf" and guess_mimetype(document_path) != 'application/pdf': - document_path = render_document(document_path, extension="pdf") - dispose_input = True - - # spawn subprocess and render the document - output_path = None - if settings.UNOCONV_ENABLE: - timeout = None - _, output_path = tempfile.mkstemp(suffix=f".{extension}") - try: - unoconv = subprocess.Popen( - [settings.UNOCONV_EXECUTABLE, "-v", "-e", "PageRange=1-2", - "-f", extension, "-o", output_path, document_path], - stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - timeout = Timer(settings.UNOCONV_TIMEOUT, unoconv.kill) - timeout.start() - stdout, stderr = unoconv.communicate() - except Exception as e: - traceback.print_exc() - raise ConversionError(str(e)) - finally: - if timeout: - timeout.cancel() - if dispose_input and document_path is not None: - os.remove(document_path) - else: - raise NotImplementedError("unoconv is disabled. Set 'UNOCONV_ENABLE' to enable.") - - return output_path - - -def generate_thumbnail_content(image_path, size=(200, 150)): - """Generate thumbnail content from an image file. - - Return the entire content of the image file. - """ - - try: - from PIL import Image, ImageOps - except ImportError: - raise MissingPILError() - - try: - image = Image.open(image_path) - source_width, source_height = image.size - target_width, target_height = size - - if source_width != target_width or source_width != target_height: - image = ImageOps.fit(image, size, Image.ANTIALIAS) - - with io.BytesIO() as output: - image.save(output, format='PNG') - content = output.getvalue() - output.close() - return content - except Exception as e: - raise e diff --git a/geonode/documents/tasks.py b/geonode/documents/tasks.py index 1cd21e6bb74..d9968e8bbb9 100644 --- a/geonode/documents/tasks.py +++ b/geonode/documents/tasks.py @@ -16,6 +16,10 @@ # along with this program. If not, see . # ######################################################################### +import io + +from PIL import Image + from celery.utils.log import get_task_logger from geonode.celery_app import app @@ -23,7 +27,6 @@ from ..base.models import ResourceBase from .models import Document -from .renderers import (generate_thumbnail_content) logger = get_task_logger(__name__) @@ -61,9 +64,13 @@ def create_document_thumbnail(self, object_id): image_file = storage_manager.open(dname, 'rb') try: - thumbnail_content = generate_thumbnail_content(image_file) + image = Image.open(image_file) + with io.BytesIO() as output: + image.save(output, format='PNG') + thumbnail_content = output.getvalue() + output.close() except Exception as e: - logger.debug(f"Could not generate thumbnail, setting thumbnail_url to None: {e}") + logger.debug(f"Could not generate thumbnail: {e}") finally: if image_file is not None: image_file.close() diff --git a/geonode/documents/tests.py b/geonode/documents/tests.py index 9674da4d8a5..f5311351710 100644 --- a/geonode/documents/tests.py +++ b/geonode/documents/tests.py @@ -31,6 +31,7 @@ from io import BytesIO from unittest.mock import patch +from urllib.parse import urlparse from django.urls import reverse from django.conf import settings @@ -95,6 +96,7 @@ def tearDownClass(cls): def setUp(self): super().setUp() create_models('map') + self.project_root = os.path.abspath(os.path.dirname(__file__)) self.imgfile = io.BytesIO( b'GIF87a\x01\x00\x01\x00\x80\x01\x00\x00\x00\x00ccc,\x00' b'\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;') @@ -266,6 +268,42 @@ def test_replace_document(self): # Remove document d.delete() + def test_non_image_documents_thumbnail(self): + self.client.login(username='admin', password='admin') + try: + with open(os.path.join(f"{self.project_root}", "tests/data/text.txt"), "rb") as f: + data = { + 'title': "Non img File Doc", + 'doc_file': f, + 'extension': 'txt' + } + self.client.post(reverse('document_upload'), data=data) + d = Document.objects.get(title='Non img File Doc') + self.assertIsNone(d.thumbnail_url) + finally: + Document.objects.filter(title='Non img File Doc').delete() + + def test_image_documents_thumbnail(self): + self.client.login(username='admin', password='admin') + try: + with open(os.path.join(f"{self.project_root}", "tests/data/img.gif"), "rb") as f: + data = { + 'title': "img File Doc", + 'doc_file': f, + 'extension': 'gif', + } + with self.settings(THUMBNAIL_SIZE={'width': 400, 'height': 200}): + self.client.post(reverse('document_upload'), data=data) + d = Document.objects.get(title='img File Doc') + self.assertIsNotNone(d.thumbnail_url) + thumb_file = os.path.join( + settings.MEDIA_ROOT, f"thumbs/{os.path.basename(urlparse(d.thumbnail_url).path)}" + ) + file = Image.open(thumb_file) + self.assertEqual(file.size, (400, 200)) + finally: + Document.objects.filter(title='img File Doc').delete() + def test_upload_document_form_size_limit(self): form_data = { 'title': 'GeoNode Map', diff --git a/geonode/documents/tests/data/text.txt b/geonode/documents/tests/data/text.txt new file mode 100644 index 00000000000..2a02d41ce21 --- /dev/null +++ b/geonode/documents/tests/data/text.txt @@ -0,0 +1 @@ +TEST diff --git a/geonode/settings.py b/geonode/settings.py index a695558f362..ded3d6e80b8 100644 --- a/geonode/settings.py +++ b/geonode/settings.py @@ -1997,7 +1997,7 @@ def get_geonode_catalogue_service(): 'THUMBNAIL_GENERATOR', 'geonode.thumbs.thumbnails.create_gs_thumbnail_geonode') THUMBNAIL_SIZE = { - 'width': int(os.environ.get('THUMBNAIL_GENERATOR_DEFAULT_SIZE_WIDTH', 240)), + 'width': int(os.environ.get('THUMBNAIL_GENERATOR_DEFAULT_SIZE_WIDTH', 500)), 'height': int(os.environ.get('THUMBNAIL_GENERATOR_DEFAULT_SIZE_HEIGHT', 200)) }