mirror of
				https://github.com/sissbruecker/linkding.git
				synced 2025-11-04 04:54:09 +01:00 
			
		
		
		
	Allow searching for untagged bookmarks (#226)
Co-authored-by: Sascha Ißbrücker <sascha.issbruecker@gmail.com>
This commit is contained in:
		@@ -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,
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -15,3 +15,7 @@
 | 
			
		||||
.text-gray-dark {
 | 
			
		||||
  color: $gray-color-dark;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.align-baseline {
 | 
			
		||||
  align-items: baseline;
 | 
			
		||||
}
 | 
			
		||||
@@ -33,8 +33,10 @@
 | 
			
		||||
 | 
			
		||||
        {# Tag list #}
 | 
			
		||||
        <section class="content-area column col-4 hide-md">
 | 
			
		||||
            <div class="content-area-header">
 | 
			
		||||
            <div class="content-area-header align-baseline">
 | 
			
		||||
                <h2>Tags</h2>
 | 
			
		||||
                <div class="spacer"></div>
 | 
			
		||||
                <a href="?q=!untagged" class="text-gray text-sm">Show Untagged</a>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% tag_cloud tags %}
 | 
			
		||||
        </section>
 | 
			
		||||
 
 | 
			
		||||
@@ -33,8 +33,10 @@
 | 
			
		||||
 | 
			
		||||
        {# Tag list #}
 | 
			
		||||
        <section class="content-area column col-4 hide-md">
 | 
			
		||||
            <div class="content-area-header">
 | 
			
		||||
            <div class="content-area-header align-baseline">
 | 
			
		||||
                <h2>Tags</h2>
 | 
			
		||||
                <div class="spacer"></div>
 | 
			
		||||
                <a href="?q=!untagged" class="text-gray text-sm">Show Untagged</a>
 | 
			
		||||
            </div>
 | 
			
		||||
            {% tag_cloud tags %}
 | 
			
		||||
        </section>
 | 
			
		||||
 
 | 
			
		||||
@@ -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, '<a href="?q=!untagged" class="text-gray text-sm">Show Untagged</a>')
 | 
			
		||||
 
 | 
			
		||||
@@ -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), [])
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user