From 573b6f5411eaaa18ccc9524544fa27bb594c532b Mon Sep 17 00:00:00 2001 From: Daniel Federschmidt Date: Sat, 28 Feb 2026 10:54:09 +0100 Subject: [PATCH] Filter bundles by reading or sharing state (#1308) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add filtering by reading or sharing state * fix migration * add tests * format * fix model references * replace hard-coded strings in tests --------- Co-authored-by: dfederschmidt Co-authored-by: Sascha Ißbrücker --- bookmarks/admin.py | 2 + bookmarks/api/serializers.py | 2 + bookmarks/forms.py | 20 ++++- ...4_bookmarkbundle_filter_shared_and_more.py | 30 +++++++ bookmarks/models.py | 26 ++++++ bookmarks/queries.py | 10 +++ bookmarks/templates/bundles/form.html | 14 +++ bookmarks/tests/helpers.py | 4 + bookmarks/tests/test_bundles_api.py | 12 +++ bookmarks/tests/test_bundles_edit_view.py | 31 +++++++ bookmarks/tests/test_bundles_new_view.py | 4 + bookmarks/tests/test_bundles_preview_view.py | 63 ++++++++++++++ bookmarks/tests/test_queries.py | 85 ++++++++++++++++++- 13 files changed, 301 insertions(+), 2 deletions(-) create mode 100644 bookmarks/migrations/0054_bookmarkbundle_filter_shared_and_more.py diff --git a/bookmarks/admin.py b/bookmarks/admin.py index 01776a6..1df9cbd 100644 --- a/bookmarks/admin.py +++ b/bookmarks/admin.py @@ -278,6 +278,8 @@ class AdminBookmarkBundle(admin.ModelAdmin): "any_tags", "all_tags", "excluded_tags", + "filter_shared", + "filter_unread", "date_created", ) search_fields = ["name", "search", "any_tags", "all_tags", "excluded_tags"] diff --git a/bookmarks/api/serializers.py b/bookmarks/api/serializers.py index 6d521fe..d1208f2 100644 --- a/bookmarks/api/serializers.py +++ b/bookmarks/api/serializers.py @@ -44,6 +44,8 @@ class BookmarkBundleSerializer(serializers.ModelSerializer): "any_tags", "all_tags", "excluded_tags", + "filter_unread", + "filter_shared", "order", "date_created", "date_modified", diff --git a/bookmarks/forms.py b/bookmarks/forms.py index d5dd032..8510615 100644 --- a/bookmarks/forms.py +++ b/bookmarks/forms.py @@ -218,10 +218,28 @@ class BookmarkBundleForm(forms.ModelForm): any_tags = forms.CharField(required=False, widget=TagAutocomplete) all_tags = forms.CharField(required=False, widget=TagAutocomplete) excluded_tags = forms.CharField(required=False, widget=TagAutocomplete) + filter_unread = forms.ChoiceField( + choices=BookmarkBundle.FILTER_UNREAD_CHOICES, + required=False, + widget=FormSelect, + ) + filter_shared = forms.ChoiceField( + choices=BookmarkBundle.FILTER_SHARED_CHOICES, + required=False, + widget=FormSelect, + ) class Meta: model = BookmarkBundle - fields = ["name", "search", "any_tags", "all_tags", "excluded_tags"] + fields = [ + "name", + "search", + "any_tags", + "all_tags", + "excluded_tags", + "filter_unread", + "filter_shared", + ] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs, error_class=FormErrorList) diff --git a/bookmarks/migrations/0054_bookmarkbundle_filter_shared_and_more.py b/bookmarks/migrations/0054_bookmarkbundle_filter_shared_and_more.py new file mode 100644 index 0000000..7711005 --- /dev/null +++ b/bookmarks/migrations/0054_bookmarkbundle_filter_shared_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 6.0 on 2026-02-28 09:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("bookmarks", "0053_migrate_api_tokens"), + ] + + operations = [ + migrations.AddField( + model_name="bookmarkbundle", + name="filter_shared", + field=models.CharField( + choices=[("off", "All"), ("yes", "Shared"), ("no", "Unshared")], + default="off", + max_length=3, + ), + ), + migrations.AddField( + model_name="bookmarkbundle", + name="filter_unread", + field=models.CharField( + choices=[("off", "All"), ("yes", "Unread"), ("no", "Read")], + default="off", + max_length=3, + ), + ), + ] diff --git a/bookmarks/models.py b/bookmarks/models.py index dfa4614..2d8c58f 100644 --- a/bookmarks/models.py +++ b/bookmarks/models.py @@ -181,11 +181,37 @@ def bookmark_asset_deleted(sender, instance, **kwargs): class BookmarkBundle(models.Model): + FILTER_STATE_OFF = "off" + FILTER_STATE_YES = "yes" + FILTER_STATE_NO = "no" + FILTER_UNREAD_CHOICES = [ + (FILTER_STATE_OFF, "All"), + (FILTER_STATE_YES, "Unread"), + (FILTER_STATE_NO, "Read"), + ] + FILTER_SHARED_CHOICES = [ + (FILTER_STATE_OFF, "All"), + (FILTER_STATE_YES, "Shared"), + (FILTER_STATE_NO, "Unshared"), + ] + name = models.CharField(max_length=256, blank=False) search = models.CharField(max_length=256, blank=True) any_tags = models.CharField(max_length=1024, blank=True) all_tags = models.CharField(max_length=1024, blank=True) excluded_tags = models.CharField(max_length=1024, blank=True) + filter_unread = models.CharField( + max_length=3, + choices=FILTER_UNREAD_CHOICES, + blank=False, + default=FILTER_STATE_OFF, + ) + filter_shared = models.CharField( + max_length=3, + choices=FILTER_SHARED_CHOICES, + blank=False, + default=FILTER_STATE_OFF, + ) order = models.IntegerField(null=False, default=0) date_created = models.DateTimeField(auto_now_add=True, null=False) date_modified = models.DateTimeField(auto_now=True, null=False) diff --git a/bookmarks/queries.py b/bookmarks/queries.py index 5780f30..5fa70dc 100644 --- a/bookmarks/queries.py +++ b/bookmarks/queries.py @@ -211,6 +211,16 @@ def _filter_bundle(query_set: QuerySet, bundle: BookmarkBundle) -> QuerySet: Exists(Bookmark.objects.filter(tag_conditions, id=OuterRef("id"))) ) + if bundle.filter_unread == BookmarkBundle.FILTER_STATE_YES: + query_set = query_set.filter(unread=True) + elif bundle.filter_unread == BookmarkBundle.FILTER_STATE_NO: + query_set = query_set.filter(unread=False) + + if bundle.filter_shared == BookmarkBundle.FILTER_STATE_YES: + query_set = query_set.filter(shared=True) + elif bundle.filter_shared == BookmarkBundle.FILTER_STATE_NO: + query_set = query_set.filter(shared=False) + return query_set diff --git a/bookmarks/templates/bundles/form.html b/bookmarks/templates/bundles/form.html index 7135407..b101766 100644 --- a/bookmarks/templates/bundles/form.html +++ b/bookmarks/templates/bundles/form.html @@ -33,6 +33,20 @@ None of these tags must be present in a bookmark to match. {% endformhelp %} +
+ {% formlabel form.filter_unread "Reading State" %} + {% formfield form.filter_unread has_help=True %} + {% formhelp form.filter_unread %} + Limit matches to unread or read bookmarks. + {% endformhelp %} +
+
+ {% formlabel form.filter_shared "Sharing State" %} + {% formfield form.filter_shared has_help=True %} + {% formhelp form.filter_shared %} + Limit matches to shared or unshared bookmarks. + {% endformhelp %} +