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 %} +