From 93faf70b37d8f99dcb00a6d7be65c6144cca59fc Mon Sep 17 00:00:00 2001 From: Per Mortensen Date: Sun, 10 Aug 2025 08:38:18 +0200 Subject: [PATCH] Use filename when downloading asset through UI (#1146) --- bookmarks/api/routes.py | 9 ++--- bookmarks/models.py | 8 ++++ bookmarks/tests/test_bookmark_asset_view.py | 43 +++++++++++++++++++-- bookmarks/views/assets.py | 4 +- 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/bookmarks/api/routes.py b/bookmarks/api/routes.py index dd87363..562d007 100644 --- a/bookmarks/api/routes.py +++ b/bookmarks/api/routes.py @@ -199,13 +199,10 @@ class BookmarkAssetViewSet( if asset.gzip else open(file_path, "rb") ) - file_name = ( - f"{asset.display_name}.html" - if asset.asset_type == BookmarkAsset.TYPE_SNAPSHOT - else asset.display_name - ) response = StreamingHttpResponse(file_stream, content_type=content_type) - response["Content-Disposition"] = f'attachment; filename="{file_name}"' + response["Content-Disposition"] = ( + f'attachment; filename="{asset.download_name}"' + ) return response except FileNotFoundError: raise Http404("Asset file does not exist") diff --git a/bookmarks/models.py b/bookmarks/models.py index 375c0cd..15fa0fa 100644 --- a/bookmarks/models.py +++ b/bookmarks/models.py @@ -133,6 +133,14 @@ class BookmarkAsset(models.Model): status = models.CharField(max_length=64, blank=False, null=False) gzip = models.BooleanField(default=False, null=False) + @property + def download_name(self): + return ( + f"{self.display_name}.html" + if self.asset_type == BookmarkAsset.TYPE_SNAPSHOT + else self.display_name + ) + def save(self, *args, **kwargs): if self.file: try: diff --git a/bookmarks/tests/test_bookmark_asset_view.py b/bookmarks/tests/test_bookmark_asset_view.py index 17710eb..f192305 100644 --- a/bookmarks/tests/test_bookmark_asset_view.py +++ b/bookmarks/tests/test_bookmark_asset_view.py @@ -4,9 +4,8 @@ from django.conf import settings from django.test import TestCase from django.urls import reverse -from bookmarks.tests.helpers import ( - BookmarkFactoryMixin, -) +from bookmarks.models import BookmarkAsset +from bookmarks.tests.helpers import BookmarkFactoryMixin class BookmarkAssetViewTestCase(TestCase, BookmarkFactoryMixin): @@ -23,7 +22,21 @@ class BookmarkAssetViewTestCase(TestCase, BookmarkFactoryMixin): def setup_asset_with_file(self, bookmark): filename = f"temp_{bookmark.id}.html.gzip" self.setup_asset_file(filename) - asset = self.setup_asset(bookmark=bookmark, file=filename) + asset = self.setup_asset( + bookmark=bookmark, file=filename, display_name=f"Snapshot {bookmark.id}" + ) + return asset + + def setup_asset_with_uploaded_file(self, bookmark): + filename = f"temp_{bookmark.id}.png.gzip" + self.setup_asset_file(filename) + asset = self.setup_asset( + bookmark=bookmark, + file=filename, + asset_type=BookmarkAsset.TYPE_UPLOAD, + content_type="image/png", + display_name=f"Uploaded file {bookmark.id}.png", + ) return asset def view_access_test(self, view_name: str): @@ -127,3 +140,25 @@ class BookmarkAssetViewTestCase(TestCase, BookmarkFactoryMixin): def test_reader_view_access_guest_user(self): self.view_access_guest_user_test("linkding:assets.read") + + def test_snapshot_download_name(self): + bookmark = self.setup_bookmark() + asset = self.setup_asset_with_file(bookmark) + response = self.client.get(reverse("linkding:assets.view", args=[asset.id])) + + self.assertEqual(response["Content-Type"], asset.content_type) + self.assertEqual( + response["Content-Disposition"], + f'inline; filename="{asset.display_name}.html"', + ) + + def test_uploaded_file_download_name(self): + bookmark = self.setup_bookmark() + asset = self.setup_asset_with_uploaded_file(bookmark) + response = self.client.get(reverse("linkding:assets.view", args=[asset.id])) + + self.assertEqual(response["Content-Type"], asset.content_type) + self.assertEqual( + response["Content-Disposition"], + f'inline; filename="{asset.display_name}"', + ) diff --git a/bookmarks/views/assets.py b/bookmarks/views/assets.py index c41d957..ae6f8b0 100644 --- a/bookmarks/views/assets.py +++ b/bookmarks/views/assets.py @@ -31,7 +31,9 @@ def view(request, asset_id: int): asset = access.asset_read(request, asset_id) content = _get_asset_content(asset) - return HttpResponse(content, content_type=asset.content_type) + response = HttpResponse(content, content_type=asset.content_type) + response["Content-Disposition"] = f'inline; filename="{asset.download_name}"' + return response def read(request, asset_id: int):