diff --git a/bookmarks/queries.py b/bookmarks/queries.py index 17a7a35..016bb59 100644 --- a/bookmarks/queries.py +++ b/bookmarks/queries.py @@ -55,6 +55,12 @@ def _base_bookmarks_query(user: User, query_string: str) -> QuerySet: tags__name__iexact=tag_name ) + # Untagged bookmarks + if query['untagged']: + query_set = query_set.filter( + tags=None + ) + # Sort by date added query_set = query_set.order_by('-date_added') @@ -90,11 +96,15 @@ def _parse_query_string(query_string): keywords = query_string.strip().split(' ') keywords = [word for word in keywords if word] - search_terms = [word for word in keywords if word[0] != '#'] + search_terms = [word for word in keywords if word[0] != '#' and word[0] != '!'] tag_names = [word[1:] for word in keywords if word[0] == '#'] tag_names = unique(tag_names, str.lower) + # Special search commands + untagged = '!untagged' in keywords + return { 'search_terms': search_terms, 'tag_names': tag_names, + 'untagged': untagged, } diff --git a/bookmarks/styles/util.scss b/bookmarks/styles/util.scss index ad08a70..1108e5b 100644 --- a/bookmarks/styles/util.scss +++ b/bookmarks/styles/util.scss @@ -15,3 +15,7 @@ .text-gray-dark { color: $gray-color-dark; } + +.align-baseline { + align-items: baseline; +} \ No newline at end of file diff --git a/bookmarks/templates/bookmarks/archive.html b/bookmarks/templates/bookmarks/archive.html index 0c0c5cd..8e6e280 100644 --- a/bookmarks/templates/bookmarks/archive.html +++ b/bookmarks/templates/bookmarks/archive.html @@ -33,8 +33,10 @@ {# Tag list #}
-
+

Tags

+
+ Show Untagged
{% tag_cloud tags %}
diff --git a/bookmarks/templates/bookmarks/index.html b/bookmarks/templates/bookmarks/index.html index 80ddde4..5f7e268 100644 --- a/bookmarks/templates/bookmarks/index.html +++ b/bookmarks/templates/bookmarks/index.html @@ -33,8 +33,10 @@ {# Tag list #}
-
+

Tags

+
+ Show Untagged
{% tag_cloud tags %}
diff --git a/bookmarks/tests/test_bookmark_index_view.py b/bookmarks/tests/test_bookmark_index_view.py index 4e06de0..145eed1 100644 --- a/bookmarks/tests/test_bookmark_index_view.py +++ b/bookmarks/tests/test_bookmark_index_view.py @@ -156,3 +156,8 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin): response = self.client.get(reverse('bookmarks:index')) self.assertVisibleBookmarks(response, visible_bookmarks, '_self') + + def test_should_show_link_for_untagged_bookmarks(self): + response = self.client.get(reverse('bookmarks:index')) + + self.assertContains(response, 'Show Untagged') diff --git a/bookmarks/tests/test_queries.py b/bookmarks/tests/test_queries.py index 542828e..a346ca9 100644 --- a/bookmarks/tests/test_queries.py +++ b/bookmarks/tests/test_queries.py @@ -289,6 +289,60 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin): self.assertEqual(bookmark.tag_string, None) self.assertTrue(bookmark.tag_projection) + def test_query_bookmarks_untagged_should_return_untagged_bookmarks_only(self): + tag = self.setup_tag() + untagged_bookmark = self.setup_bookmark() + self.setup_bookmark(tags=[tag]) + self.setup_bookmark(tags=[tag]) + + query = queries.query_bookmarks(self.user, '!untagged') + self.assertCountEqual(list(query), [untagged_bookmark]) + + def test_query_bookmarks_untagged_should_be_combinable_with_search_terms(self): + tag = self.setup_tag() + untagged_bookmark = self.setup_bookmark(title='term1') + self.setup_bookmark(title='term2') + self.setup_bookmark(tags=[tag]) + + query = queries.query_bookmarks(self.user, '!untagged term1') + self.assertCountEqual(list(query), [untagged_bookmark]) + + def test_query_bookmarks_untagged_should_not_be_combinable_with_tags(self): + tag = self.setup_tag() + self.setup_bookmark() + self.setup_bookmark(tags=[tag]) + self.setup_bookmark(tags=[tag]) + + query = queries.query_bookmarks(self.user, f'!untagged #{tag.name}') + self.assertCountEqual(list(query), []) + + def test_query_archived_bookmarks_untagged_should_return_untagged_bookmarks_only(self): + tag = self.setup_tag() + untagged_bookmark = self.setup_bookmark(is_archived=True) + self.setup_bookmark(is_archived=True, tags=[tag]) + self.setup_bookmark(is_archived=True, tags=[tag]) + + query = queries.query_archived_bookmarks(self.user, '!untagged') + self.assertCountEqual(list(query), [untagged_bookmark]) + + def test_query_archived_bookmarks_untagged_should_be_combinable_with_search_terms(self): + tag = self.setup_tag() + untagged_bookmark = self.setup_bookmark(is_archived=True, title='term1') + self.setup_bookmark(is_archived=True, title='term2') + self.setup_bookmark(is_archived=True, tags=[tag]) + + query = queries.query_archived_bookmarks(self.user, '!untagged term1') + self.assertCountEqual(list(query), [untagged_bookmark]) + + def test_query_archived_bookmarks_untagged_should_not_be_combinable_with_tags(self): + tag = self.setup_tag() + self.setup_bookmark(is_archived=True) + self.setup_bookmark(is_archived=True, tags=[tag]) + self.setup_bookmark(is_archived=True, tags=[tag]) + + query = queries.query_archived_bookmarks(self.user, f'!untagged #{tag.name}') + self.assertCountEqual(list(query), []) + def test_query_bookmark_tags_should_return_all_tags_for_empty_query(self): self.setup_tag_search_data() @@ -460,3 +514,35 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin): query = queries.query_archived_bookmark_tags(self.user, '') self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)]) + + def test_query_bookmark_tags_untagged_should_never_return_any_tags(self): + tag = self.setup_tag() + self.setup_bookmark() + self.setup_bookmark(title='term1') + self.setup_bookmark(title='term1', tags=[tag]) + self.setup_bookmark(tags=[tag]) + + query = queries.query_bookmark_tags(self.user, '!untagged') + self.assertCountEqual(list(query), []) + + query = queries.query_bookmark_tags(self.user, '!untagged term1') + self.assertCountEqual(list(query), []) + + query = queries.query_bookmark_tags(self.user, f'!untagged #{tag.name}') + self.assertCountEqual(list(query), []) + + def test_query_archived_bookmark_tags_untagged_should_never_return_any_tags(self): + tag = self.setup_tag() + self.setup_bookmark(is_archived=True) + self.setup_bookmark(is_archived=True, title='term1') + self.setup_bookmark(is_archived=True, title='term1', tags=[tag]) + self.setup_bookmark(is_archived=True, tags=[tag]) + + query = queries.query_archived_bookmark_tags(self.user, '!untagged') + self.assertCountEqual(list(query), []) + + query = queries.query_archived_bookmark_tags(self.user, '!untagged term1') + self.assertCountEqual(list(query), []) + + query = queries.query_archived_bookmark_tags(self.user, f'!untagged #{tag.name}') + self.assertCountEqual(list(query), [])