From 5cc8c9c010175e9f438d9c1c31117b7ce735a46a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20I=C3=9Fbr=C3=BCcker?= Date: Sun, 10 Aug 2025 12:59:55 +0200 Subject: [PATCH] Allow filtering feeds by bundle (#1152) --- bookmarks/feeds.py | 7 ++++++ bookmarks/tests/test_feeds.py | 47 +++++++++++++++++++++++++++++++++++ bookmarks/views/access.py | 6 ++++- 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/bookmarks/feeds.py b/bookmarks/feeds.py index ec8308e..572c617 100644 --- a/bookmarks/feeds.py +++ b/bookmarks/feeds.py @@ -8,6 +8,7 @@ from django.urls import reverse from bookmarks import queries from bookmarks.models import Bookmark, BookmarkSearch, FeedToken, UserProfile +from bookmarks.views import access @dataclass @@ -30,10 +31,16 @@ def sanitize(text: str): class BaseBookmarksFeed(Feed): def get_object(self, request, feed_key: str | None): feed_token = FeedToken.objects.get(key__exact=feed_key) if feed_key else None + bundle = None + bundle_id = request.GET.get("bundle") + if bundle_id: + bundle = access.bundle_read(request, bundle_id) + search = BookmarkSearch( q=request.GET.get("q", ""), unread=request.GET.get("unread", ""), shared=request.GET.get("shared", ""), + bundle=bundle, ) query_set = self.get_query_set(feed_token, search) return FeedContext(request, feed_token, query_set) diff --git a/bookmarks/tests/test_feeds.py b/bookmarks/tests/test_feeds.py index 4e78107..f8321e8 100644 --- a/bookmarks/tests/test_feeds.py +++ b/bookmarks/tests/test_feeds.py @@ -357,3 +357,50 @@ class FeedsTestCase(TestCase, BookmarkFactoryMixin): def test_sanitize_with_none_text(self): self.assertEqual("", sanitize(None)) + + def test_with_bundle(self): + tag1 = self.setup_tag() + visible_bookmarks = [ + self.setup_bookmark(tags=[tag1]), + self.setup_bookmark(tags=[tag1]), + ] + + self.setup_bookmark(), + self.setup_bookmark(), + + bundle = self.setup_bundle(all_tags=tag1.name) + + response = self.client.get( + reverse("linkding:feeds.all", args=[self.token.key]) + + f"?bundle={bundle.id}" + ) + self.assertEqual(response.status_code, 200) + self.assertFeedItems(response, visible_bookmarks) + + def test_with_bundle_not_owned_by_user(self): + other_user = User.objects.create_user( + "otheruser", "otheruser@example.com", "password123" + ) + other_bundle = self.setup_bundle(user=other_user, search="test") + + response = self.client.get( + reverse("linkding:feeds.all", args=[self.token.key]) + + f"?bundle={other_bundle.id}" + ) + self.assertEqual(response.status_code, 404) + + def test_with_invalid_bundle_id(self): + self.setup_bookmark(title="test bookmark") + + response = self.client.get( + reverse("linkding:feeds.all", args=[self.token.key]) + "?bundle=999999" + ) + self.assertEqual(response.status_code, 404) + + def test_with_non_numeric_bundle_id(self): + self.setup_bookmark(title="test bookmark") + + response = self.client.get( + reverse("linkding:feeds.all", args=[self.token.key]) + "?bundle=invalid" + ) + self.assertEqual(response.status_code, 404) diff --git a/bookmarks/views/access.py b/bookmarks/views/access.py index 0228d4a..3452bbc 100644 --- a/bookmarks/views/access.py +++ b/bookmarks/views/access.py @@ -32,10 +32,14 @@ def bookmark_write(request: HttpRequest, bookmark_id: int | str): raise Http404("Bookmark does not exist") +def bundle_read(request: HttpRequest, bundle_id: int | str): + return bundle_write(request, bundle_id) + + def bundle_write(request: HttpRequest, bundle_id: int | str): try: return BookmarkBundle.objects.get(pk=bundle_id, owner=request.user) - except BookmarkBundle.DoesNotExist: + except (BookmarkBundle.DoesNotExist, ValueError): raise Http404("Bundle does not exist")