diff --git a/bookmarks/api/routes.py b/bookmarks/api/routes.py index 5249c51..5aa7f92 100644 --- a/bookmarks/api/routes.py +++ b/bookmarks/api/routes.py @@ -52,7 +52,7 @@ class BookmarkViewSet( return Bookmark.objects.all().filter(owner=user) def get_serializer_context(self): - return {"user": self.request.user} + return {"request": self.request, "user": self.request.user} @action(methods=["get"], detail=False) def archived(self, request): @@ -60,8 +60,8 @@ class BookmarkViewSet( search = BookmarkSearch.from_request(request.GET) query_set = queries.query_archived_bookmarks(user, user.profile, search) page = self.paginate_queryset(query_set) - serializer = self.get_serializer_class() - data = serializer(page, many=True).data + serializer = self.get_serializer(page, many=True) + data = serializer.data return self.get_paginated_response(data) @action(methods=["get"], detail=False) @@ -73,8 +73,8 @@ class BookmarkViewSet( user, request.user_profile, search, public_only ) page = self.paginate_queryset(query_set) - serializer = self.get_serializer_class() - data = serializer(page, many=True).data + serializer = self.get_serializer(page, many=True) + data = serializer.data return self.get_paginated_response(data) @action(methods=["post"], detail=True) diff --git a/bookmarks/api/serializers.py b/bookmarks/api/serializers.py index 6e82e33..38f19b9 100644 --- a/bookmarks/api/serializers.py +++ b/bookmarks/api/serializers.py @@ -1,4 +1,5 @@ from django.db.models import prefetch_related_objects +from django.templatetags.static import static from rest_framework import serializers from rest_framework.serializers import ListSerializer @@ -31,6 +32,8 @@ class BookmarkSerializer(serializers.ModelSerializer): "website_title", "website_description", "web_archive_snapshot_url", + "favicon_url", + "preview_image_url", "is_archived", "unread", "shared", @@ -42,6 +45,8 @@ class BookmarkSerializer(serializers.ModelSerializer): "website_title", "website_description", "web_archive_snapshot_url", + "favicon_url", + "preview_image_url", "date_added", "date_modified", ] @@ -56,6 +61,24 @@ class BookmarkSerializer(serializers.ModelSerializer): shared = serializers.BooleanField(required=False, default=False) # Override readonly tag_names property to allow passing a list of tag names to create/update 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): bookmark = Bookmark() diff --git a/bookmarks/tests/helpers.py b/bookmarks/tests/helpers.py index 4cd160d..1e8b092 100644 --- a/bookmarks/tests/helpers.py +++ b/bookmarks/tests/helpers.py @@ -87,6 +87,8 @@ class BookmarkFactoryMixin: shared: bool = False, with_tags: 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 or self.get_or_create_test_user() @@ -118,6 +120,12 @@ class BookmarkFactoryMixin: web_archive_snapshot_url = "" if with_web_archive_snapshot_url: 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( url=url, title=title, @@ -126,6 +134,8 @@ class BookmarkFactoryMixin: shared=shared, tags=tags, web_archive_snapshot_url=web_archive_snapshot_url, + favicon_file=favicon_file, + preview_image_file=preview_image_file, user=user, ) bookmarks.append(bookmark) diff --git a/bookmarks/tests/test_bookmarks_api.py b/bookmarks/tests/test_bookmarks_api.py index 58448df..68bd1a2 100644 --- a/bookmarks/tests/test_bookmarks_api.py +++ b/bookmarks/tests/test_bookmarks_api.py @@ -36,6 +36,16 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): expectation["website_title"] = bookmark.website_title expectation["website_description"] = bookmark.website_description 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["unread"] = bookmark.unread expectation["shared"] = bookmark.shared @@ -65,7 +75,11 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): def test_list_bookmarks_with_more_details(self): self.authenticate() 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( @@ -171,6 +185,23 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): ) 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): self.authenticate() search_value = self.get_random_string() @@ -220,6 +251,26 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): ) 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): user1 = self.setup_user(enable_sharing=True, enable_public_sharing=True) user2 = self.setup_user(enable_sharing=True) @@ -701,6 +752,8 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): url="https://example.com", title="Example title", description="Example description", + favicon_file="favicon.png", + preview_image_file="preview.png", ) url = reverse("bookmarks:bookmark-check") @@ -715,6 +768,12 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin): self.assertEqual(bookmark.url, bookmark_data["url"]) self.assertEqual(bookmark.title, bookmark_data["title"]) 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): self.authenticate() diff --git a/docs/API.md b/docs/API.md index 8099f51..1af04a8 100644 --- a/docs/API.md +++ b/docs/API.md @@ -49,6 +49,8 @@ Example response: "website_title": "Website title", "website_description": "Website description", "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, "unread": false, "shared": false,