diff --git a/CHANGELOG.md b/CHANGELOG.md index ab886bcf..6df31f60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Features - [CSV Reports] Add fields "created_date", "modified_date", "finished_date" to issues CSV report. +- [Attachment] Generate 'card-image' size (300x200) thumbnails for attached image files. ### Misc - Improve login and forgot password: allow username or email case-insensitive if the query only diff --git a/settings/common.py b/settings/common.py index 355a6597..e1500ce8 100644 --- a/settings/common.py +++ b/settings/common.py @@ -448,12 +448,15 @@ SOUTH_MIGRATION_MODULES = { DEFAULT_AVATAR_SIZE = 80 # 80x80 pixels DEFAULT_BIG_AVATAR_SIZE = 300 # 300x300 pixels DEFAULT_TIMELINE_IMAGE_SIZE = 640 # 640x??? pixels +DEFAUL_CARD_IMAGE_WIDTH = 300 # 300 pixels +DEFAUL_CARD_IMAGE_HEIGHT = 200 # 200 pixels THUMBNAIL_ALIASES = { '': { 'avatar': {'size': (DEFAULT_AVATAR_SIZE, DEFAULT_AVATAR_SIZE), 'crop': True}, 'big-avatar': {'size': (DEFAULT_BIG_AVATAR_SIZE, DEFAULT_BIG_AVATAR_SIZE), 'crop': True}, 'timeline-image': {'size': (DEFAULT_TIMELINE_IMAGE_SIZE, 0), 'crop': True}, + 'card-image': {'size': (DEFAUL_CARD_IMAGE_WIDTH, DEFAUL_CARD_IMAGE_HEIGHT), 'crop': True}, }, } diff --git a/taiga/projects/attachments/serializers.py b/taiga/projects/attachments/serializers.py index 549acabd..42dc30d2 100644 --- a/taiga/projects/attachments/serializers.py +++ b/taiga/projects/attachments/serializers.py @@ -14,28 +14,26 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from os import path -import hashlib - -from django.conf import settings - from taiga.base.api import serializers -from taiga.base.utils.urls import reverse - +from . import services from . import models class AttachmentSerializer(serializers.ModelSerializer): url = serializers.SerializerMethodField("get_url") + thumbnail_card_url = serializers.SerializerMethodField("get_thumbnail_card_url") attached_file = serializers.FileField(required=True) class Meta: model = models.Attachment - fields = ("id", "project", "owner", "name", "attached_file", "size", "url", - "description", "is_deprecated", "created_date", "modified_date", - "object_id", "order", "sha1") + fields = ("id", "project", "owner", "name", "attached_file", "size", + "url", "thumbnail_card_url", "description", "is_deprecated", + "created_date", "modified_date", "object_id", "order", "sha1") read_only_fields = ("owner", "created_date", "modified_date", "sha1") def get_url(self, obj): - return obj.attached_file.url \ No newline at end of file + return obj.attached_file.url + + def get_thumbnail_card_url(self, obj): + return services.get_card_image_thumbnailer_url(obj) diff --git a/taiga/projects/attachments/services.py b/taiga/projects/attachments/services.py new file mode 100644 index 00000000..6dc70160 --- /dev/null +++ b/taiga/projects/attachments/services.py @@ -0,0 +1,36 @@ +# Copyright (C) 2014-2015 Taiga Agile LLC +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from taiga.base.utils.urls import get_absolute_url + +from easy_thumbnails.files import get_thumbnailer +from easy_thumbnails.exceptions import InvalidImageFormatError + + +def _get_attachment_thumbnailer_url(attachment, thumbnailer_size): + try: + thumb_url = get_thumbnailer(attachment.attached_file)[thumbnailer_size].url + thumb_url = get_absolute_url(thumb_url) + except InvalidImageFormatError: + thumb_url = None + + return thumb_url + + +def get_timeline_image_thumbnailer_url(attachment): + return _get_attachment_thumbnailer_url(attachment, "timeline-image") + + +def get_card_image_thumbnailer_url(attachment): + return _get_attachment_thumbnailer_url(attachment, "card-image") diff --git a/taiga/projects/history/freeze_impl.py b/taiga/projects/history/freeze_impl.py index a51d3011..6e327b52 100644 --- a/taiga/projects/history/freeze_impl.py +++ b/taiga/projects/history/freeze_impl.py @@ -21,14 +21,13 @@ from django.apps import apps from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist -from easy_thumbnails.files import get_thumbnailer -from easy_thumbnails.exceptions import InvalidImageFormatError - from taiga.base.utils.urls import get_absolute_url from taiga.base.utils.iterators import as_tuple from taiga.base.utils.iterators import as_dict from taiga.mdrender.service import render as mdrender +from taiga.projects.attachments.services import get_timeline_image_thumbnailer_url + import os #################### @@ -178,11 +177,7 @@ def _generic_extract(obj:object, fields:list, default=None) -> dict: @as_tuple def extract_attachments(obj) -> list: for attach in obj.attachments.all(): - try: - thumb_url = get_thumbnailer(attach.attached_file)['timeline-image'].url - thumb_url = get_absolute_url(thumb_url) - except InvalidImageFormatError as e: - thumb_url = None + thumb_url = get_timeline_image_thumbnailer_url(attach) yield {"id": attach.id, "filename": os.path.basename(attach.attached_file.name), diff --git a/tests/integration/test_users.py b/tests/integration/test_users.py index 3b435f0a..0410b579 100644 --- a/tests/integration/test_users.py +++ b/tests/integration/test_users.py @@ -216,7 +216,7 @@ def test_change_avatar_removes_the_old_one(client): thumbnailer = get_thumbnailer(user.photo) original_photo_paths = [user.photo.path] original_photo_paths += [th.path for th in thumbnailer.get_thumbnails()] - assert list(map(os.path.exists, original_photo_paths)) == [True, True, True, True] + assert list(map(os.path.exists, original_photo_paths)) == [True, True, True, True, True] client.login(user) avatar.write(DUMMY_BMP_DATA) @@ -225,7 +225,7 @@ def test_change_avatar_removes_the_old_one(client): response = client.post(url, post_data) assert response.status_code == 200 - assert list(map(os.path.exists, original_photo_paths)) == [False, False, False, False] + assert list(map(os.path.exists, original_photo_paths)) == [False, False, False, False, False] def test_remove_avatar(client): @@ -242,13 +242,13 @@ def test_remove_avatar(client): thumbnailer = get_thumbnailer(user.photo) original_photo_paths = [user.photo.path] original_photo_paths += [th.path for th in thumbnailer.get_thumbnails()] - assert list(map(os.path.exists, original_photo_paths)) == [True, True, True, True] + assert list(map(os.path.exists, original_photo_paths)) == [True, True, True, True, True] client.login(user) response = client.post(url) assert response.status_code == 200 - assert list(map(os.path.exists, original_photo_paths)) == [False, False, False, False] + assert list(map(os.path.exists, original_photo_paths)) == [False, False, False, False, False] def test_list_contacts_private_projects(client):