Include favicons and thumbnails in REST API (#763)

* Include favicons and thumbnails in REST API

* Fix serialization for custom endpoints
This commit is contained in:
Sascha Ißbrücker 2024-06-18 23:07:14 +02:00 committed by GitHub
parent b28352fb28
commit 380f5ed19c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 100 additions and 6 deletions

View file

@ -52,7 +52,7 @@ class BookmarkViewSet(
return Bookmark.objects.all().filter(owner=user) return Bookmark.objects.all().filter(owner=user)
def get_serializer_context(self): def get_serializer_context(self):
return {"user": self.request.user} return {"request": self.request, "user": self.request.user}
@action(methods=["get"], detail=False) @action(methods=["get"], detail=False)
def archived(self, request): def archived(self, request):
@ -60,8 +60,8 @@ class BookmarkViewSet(
search = BookmarkSearch.from_request(request.GET) search = BookmarkSearch.from_request(request.GET)
query_set = queries.query_archived_bookmarks(user, user.profile, search) query_set = queries.query_archived_bookmarks(user, user.profile, search)
page = self.paginate_queryset(query_set) page = self.paginate_queryset(query_set)
serializer = self.get_serializer_class() serializer = self.get_serializer(page, many=True)
data = serializer(page, many=True).data data = serializer.data
return self.get_paginated_response(data) return self.get_paginated_response(data)
@action(methods=["get"], detail=False) @action(methods=["get"], detail=False)
@ -73,8 +73,8 @@ class BookmarkViewSet(
user, request.user_profile, search, public_only user, request.user_profile, search, public_only
) )
page = self.paginate_queryset(query_set) page = self.paginate_queryset(query_set)
serializer = self.get_serializer_class() serializer = self.get_serializer(page, many=True)
data = serializer(page, many=True).data data = serializer.data
return self.get_paginated_response(data) return self.get_paginated_response(data)
@action(methods=["post"], detail=True) @action(methods=["post"], detail=True)

View file

@ -1,4 +1,5 @@
from django.db.models import prefetch_related_objects from django.db.models import prefetch_related_objects
from django.templatetags.static import static
from rest_framework import serializers from rest_framework import serializers
from rest_framework.serializers import ListSerializer from rest_framework.serializers import ListSerializer
@ -31,6 +32,8 @@ class BookmarkSerializer(serializers.ModelSerializer):
"website_title", "website_title",
"website_description", "website_description",
"web_archive_snapshot_url", "web_archive_snapshot_url",
"favicon_url",
"preview_image_url",
"is_archived", "is_archived",
"unread", "unread",
"shared", "shared",
@ -42,6 +45,8 @@ class BookmarkSerializer(serializers.ModelSerializer):
"website_title", "website_title",
"website_description", "website_description",
"web_archive_snapshot_url", "web_archive_snapshot_url",
"favicon_url",
"preview_image_url",
"date_added", "date_added",
"date_modified", "date_modified",
] ]
@ -56,6 +61,24 @@ class BookmarkSerializer(serializers.ModelSerializer):
shared = serializers.BooleanField(required=False, default=False) shared = serializers.BooleanField(required=False, default=False)
# Override readonly tag_names property to allow passing a list of tag names to create/update # Override readonly tag_names property to allow passing a list of tag names to create/update
tag_names = TagListField(required=False, default=[]) tag_names = TagListField(required=False, default=[])
favicon_url = serializers.SerializerMethodField()
preview_image_url = serializers.SerializerMethodField()
def get_favicon_url(self, obj: Bookmark):
if not obj.favicon_file:
return None
request = self.context.get("request")
favicon_file_path = static(obj.favicon_file)
favicon_url = request.build_absolute_uri(favicon_file_path)
return favicon_url
def get_preview_image_url(self, obj: Bookmark):
if not obj.preview_image_file:
return None
request = self.context.get("request")
preview_image_file_path = static(obj.preview_image_file)
preview_image_url = request.build_absolute_uri(preview_image_file_path)
return preview_image_url
def create(self, validated_data): def create(self, validated_data):
bookmark = Bookmark() bookmark = Bookmark()

View file

@ -87,6 +87,8 @@ class BookmarkFactoryMixin:
shared: bool = False, shared: bool = False,
with_tags: bool = False, with_tags: bool = False,
with_web_archive_snapshot_url: bool = False, with_web_archive_snapshot_url: bool = False,
with_favicon_file: bool = False,
with_preview_image_file: bool = False,
user: User = None, user: User = None,
): ):
user = user or self.get_or_create_test_user() user = user or self.get_or_create_test_user()
@ -118,6 +120,12 @@ class BookmarkFactoryMixin:
web_archive_snapshot_url = "" web_archive_snapshot_url = ""
if with_web_archive_snapshot_url: if with_web_archive_snapshot_url:
web_archive_snapshot_url = f"https://web.archive.org/web/{i}" web_archive_snapshot_url = f"https://web.archive.org/web/{i}"
favicon_file = ""
if with_favicon_file:
favicon_file = f"favicon_{i}.png"
preview_image_file = ""
if with_preview_image_file:
preview_image_file = f"preview_image_{i}.png"
bookmark = self.setup_bookmark( bookmark = self.setup_bookmark(
url=url, url=url,
title=title, title=title,
@ -126,6 +134,8 @@ class BookmarkFactoryMixin:
shared=shared, shared=shared,
tags=tags, tags=tags,
web_archive_snapshot_url=web_archive_snapshot_url, web_archive_snapshot_url=web_archive_snapshot_url,
favicon_file=favicon_file,
preview_image_file=preview_image_file,
user=user, user=user,
) )
bookmarks.append(bookmark) bookmarks.append(bookmark)

View file

@ -36,6 +36,16 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
expectation["website_title"] = bookmark.website_title expectation["website_title"] = bookmark.website_title
expectation["website_description"] = bookmark.website_description expectation["website_description"] = bookmark.website_description
expectation["web_archive_snapshot_url"] = bookmark.web_archive_snapshot_url expectation["web_archive_snapshot_url"] = bookmark.web_archive_snapshot_url
expectation["favicon_url"] = (
f"http://testserver/static/{bookmark.favicon_file}"
if bookmark.favicon_file
else None
)
expectation["preview_image_url"] = (
f"http://testserver/static/{bookmark.preview_image_file}"
if bookmark.preview_image_file
else None
)
expectation["is_archived"] = bookmark.is_archived expectation["is_archived"] = bookmark.is_archived
expectation["unread"] = bookmark.unread expectation["unread"] = bookmark.unread
expectation["shared"] = bookmark.shared expectation["shared"] = bookmark.shared
@ -65,7 +75,11 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
def test_list_bookmarks_with_more_details(self): def test_list_bookmarks_with_more_details(self):
self.authenticate() self.authenticate()
bookmarks = self.setup_numbered_bookmarks( bookmarks = self.setup_numbered_bookmarks(
5, with_tags=True, with_web_archive_snapshot_url=True 5,
with_tags=True,
with_web_archive_snapshot_url=True,
with_favicon_file=True,
with_preview_image_file=True,
) )
response = self.get( response = self.get(
@ -171,6 +185,23 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
) )
self.assertBookmarkListEqual(response.data["results"], archived_bookmarks) self.assertBookmarkListEqual(response.data["results"], archived_bookmarks)
def test_list_archived_bookmarks_with_more_details(self):
self.authenticate()
archived_bookmarks = self.setup_numbered_bookmarks(
5,
archived=True,
with_tags=True,
with_web_archive_snapshot_url=True,
with_favicon_file=True,
with_preview_image_file=True,
)
response = self.get(
reverse("bookmarks:bookmark-archived"),
expected_status_code=status.HTTP_200_OK,
)
self.assertBookmarkListEqual(response.data["results"], archived_bookmarks)
def test_list_archived_bookmarks_should_filter_by_query(self): def test_list_archived_bookmarks_should_filter_by_query(self):
self.authenticate() self.authenticate()
search_value = self.get_random_string() search_value = self.get_random_string()
@ -220,6 +251,26 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
) )
self.assertBookmarkListEqual(response.data["results"], shared_bookmarks) self.assertBookmarkListEqual(response.data["results"], shared_bookmarks)
def test_list_shared_bookmarks_with_more_details(self):
self.authenticate()
other_user = self.setup_user(enable_sharing=True)
shared_bookmarks = self.setup_numbered_bookmarks(
5,
shared=True,
user=other_user,
with_tags=True,
with_web_archive_snapshot_url=True,
with_favicon_file=True,
with_preview_image_file=True,
)
response = self.get(
reverse("bookmarks:bookmark-shared"),
expected_status_code=status.HTTP_200_OK,
)
self.assertBookmarkListEqual(response.data["results"], shared_bookmarks)
def test_list_only_publicly_shared_bookmarks_when_not_logged_in(self): def test_list_only_publicly_shared_bookmarks_when_not_logged_in(self):
user1 = self.setup_user(enable_sharing=True, enable_public_sharing=True) user1 = self.setup_user(enable_sharing=True, enable_public_sharing=True)
user2 = self.setup_user(enable_sharing=True) user2 = self.setup_user(enable_sharing=True)
@ -701,6 +752,8 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
url="https://example.com", url="https://example.com",
title="Example title", title="Example title",
description="Example description", description="Example description",
favicon_file="favicon.png",
preview_image_file="preview.png",
) )
url = reverse("bookmarks:bookmark-check") url = reverse("bookmarks:bookmark-check")
@ -715,6 +768,12 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
self.assertEqual(bookmark.url, bookmark_data["url"]) self.assertEqual(bookmark.url, bookmark_data["url"])
self.assertEqual(bookmark.title, bookmark_data["title"]) self.assertEqual(bookmark.title, bookmark_data["title"])
self.assertEqual(bookmark.description, bookmark_data["description"]) self.assertEqual(bookmark.description, bookmark_data["description"])
self.assertEqual(
"http://testserver/static/favicon.png", bookmark_data["favicon_url"]
)
self.assertEqual(
"http://testserver/static/preview.png", bookmark_data["preview_image_url"]
)
def test_check_returns_existing_metadata_if_url_is_bookmarked(self): def test_check_returns_existing_metadata_if_url_is_bookmarked(self):
self.authenticate() self.authenticate()

View file

@ -49,6 +49,8 @@ Example response:
"website_title": "Website title", "website_title": "Website title",
"website_description": "Website description", "website_description": "Website description",
"web_archive_snapshot_url": "https://web.archive.org/web/20200926094623/https://example.com", "web_archive_snapshot_url": "https://web.archive.org/web/20200926094623/https://example.com",
"favicon_url": "http://127.0.0.1:8000/static/https_example_com.png",
"preview_image_url": "http://127.0.0.1:8000/static/0ac5c53db923727765216a3a58e70522.jpg",
"is_archived": false, "is_archived": false,
"unread": false, "unread": false,
"shared": false, "shared": false,