mirror of
https://github.com/sissbruecker/linkding
synced 2024-11-22 03:13:02 +00:00
Add filter for unread state (#535)
This commit is contained in:
parent
28acf3299c
commit
3e36f90b38
13 changed files with 149 additions and 31 deletions
|
@ -134,12 +134,17 @@ class BookmarkSearch:
|
|||
FILTER_SHARED_SHARED = 'shared'
|
||||
FILTER_SHARED_UNSHARED = 'unshared'
|
||||
|
||||
params = ['q', 'user', 'sort', 'shared']
|
||||
FILTER_UNREAD_OFF = ''
|
||||
FILTER_UNREAD_YES = 'yes'
|
||||
FILTER_UNREAD_NO = 'no'
|
||||
|
||||
params = ['q', 'user', 'sort', 'shared', 'unread']
|
||||
defaults = {
|
||||
'q': '',
|
||||
'user': '',
|
||||
'sort': SORT_ADDED_DESC,
|
||||
'shared': FILTER_SHARED_OFF,
|
||||
'unread': FILTER_UNREAD_OFF,
|
||||
}
|
||||
|
||||
def __init__(self,
|
||||
|
@ -147,11 +152,13 @@ class BookmarkSearch:
|
|||
query: str = defaults['q'], # alias for q
|
||||
user: str = defaults['user'],
|
||||
sort: str = defaults['sort'],
|
||||
shared: str = defaults['shared']):
|
||||
shared: str = defaults['shared'],
|
||||
unread: str = defaults['unread']):
|
||||
self.q = q or query
|
||||
self.user = user
|
||||
self.sort = sort
|
||||
self.shared = shared
|
||||
self.unread = unread
|
||||
|
||||
@property
|
||||
def query(self):
|
||||
|
@ -192,11 +199,17 @@ class BookmarkSearchForm(forms.Form):
|
|||
(BookmarkSearch.FILTER_SHARED_SHARED, 'Shared'),
|
||||
(BookmarkSearch.FILTER_SHARED_UNSHARED, 'Unshared'),
|
||||
]
|
||||
FILTER_UNREAD_CHOICES = [
|
||||
(BookmarkSearch.FILTER_UNREAD_OFF, 'Off'),
|
||||
(BookmarkSearch.FILTER_UNREAD_YES, 'Unread'),
|
||||
(BookmarkSearch.FILTER_UNREAD_NO, 'Read'),
|
||||
]
|
||||
|
||||
q = forms.CharField()
|
||||
user = forms.ChoiceField()
|
||||
sort = forms.ChoiceField(choices=SORT_CHOICES)
|
||||
shared = forms.ChoiceField(choices=FILTER_SHARED_CHOICES, widget=forms.RadioSelect)
|
||||
unread = forms.ChoiceField(choices=FILTER_UNREAD_CHOICES, widget=forms.RadioSelect)
|
||||
|
||||
def __init__(self, search: BookmarkSearch, editable_fields: List[str] = None, users: List[User] = None):
|
||||
super().__init__()
|
||||
|
|
|
@ -63,12 +63,18 @@ def _base_bookmarks_query(user: Optional[User], profile: UserProfile, search: Bo
|
|||
query_set = query_set.filter(
|
||||
tags=None
|
||||
)
|
||||
# Unread bookmarks
|
||||
# Legacy unread bookmarks filter from query
|
||||
if query['unread']:
|
||||
query_set = query_set.filter(
|
||||
unread=True
|
||||
)
|
||||
|
||||
# Unread filter from bookmark search
|
||||
if search.unread == BookmarkSearch.FILTER_UNREAD_YES:
|
||||
query_set = query_set.filter(unread=True)
|
||||
elif search.unread == BookmarkSearch.FILTER_UNREAD_NO:
|
||||
query_set = query_set.filter(unread=False)
|
||||
|
||||
# Shared filter
|
||||
if search.shared == BookmarkSearch.FILTER_SHARED_SHARED:
|
||||
query_set = query_set.filter(shared=True)
|
||||
|
|
|
@ -87,6 +87,7 @@
|
|||
}
|
||||
|
||||
.radio-group {
|
||||
margin-bottom: $unit-1;
|
||||
.form-label {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</li>
|
||||
{% endif %}
|
||||
<li>
|
||||
<a href="{% url 'bookmarks:index' %}?q=!unread" class="btn btn-link">Unread</a>
|
||||
<a href="{% url 'bookmarks:index' %}?unread=yes" class="btn btn-link">Unread</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url 'bookmarks:index' %}?q=!untagged" class="btn btn-link">Untagged</a>
|
||||
|
@ -65,7 +65,7 @@
|
|||
</li>
|
||||
{% endif %}
|
||||
<li style="padding-left: 1rem">
|
||||
<a href="{% url 'bookmarks:index' %}?q=!unread" class="btn btn-link">Unread</a>
|
||||
<a href="{% url 'bookmarks:index' %}?unread=yes" class="btn btn-link">Unread</a>
|
||||
</li>
|
||||
<li style="padding-left: 1rem">
|
||||
<a href="{% url 'bookmarks:index' %}?q=!untagged" class="btn btn-link">Untagged</a>
|
||||
|
|
|
@ -37,6 +37,16 @@
|
|||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="form-group radio-group">
|
||||
<div class="form-label">Unread filter</div>
|
||||
{% for radio in form.unread %}
|
||||
<label for="{{ radio.id_for_label }}" class="form-radio form-inline">
|
||||
{{ radio.tag }}
|
||||
<i class="form-icon"></i>
|
||||
{{ radio.choice_label }}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button type="submit" class="btn btn-sm btn-primary">Apply</button>
|
||||
</div>
|
||||
|
@ -58,6 +68,7 @@
|
|||
q: '{{ search.query }}',
|
||||
user: '{{ search.user }}',
|
||||
shared: '{{ search.shared }}',
|
||||
unread: '{{ search.unread }}',
|
||||
}
|
||||
const apiClient = new linkding.ApiClient('{% url 'bookmarks:api-root' %}')
|
||||
const input = document.querySelector('#search input[name="q"]')
|
||||
|
|
|
@ -22,7 +22,7 @@ def bookmark_form(context, form: BookmarkForm, cancel_url: str, bookmark_id: int
|
|||
def bookmark_search(context, search: BookmarkSearch, tags: [Tag], mode: str = ''):
|
||||
tag_names = [tag.name for tag in tags]
|
||||
tags_string = build_tag_string(tag_names, ' ')
|
||||
form = BookmarkSearchForm(search, editable_fields=['q', 'sort', 'shared'])
|
||||
form = BookmarkSearchForm(search, editable_fields=['q', 'sort', 'shared', 'unread'])
|
||||
return {
|
||||
'request': context['request'],
|
||||
'search': search,
|
||||
|
|
|
@ -77,6 +77,7 @@ class BookmarkFactoryMixin:
|
|||
suffix: str = '',
|
||||
tag_prefix: str = '',
|
||||
archived: bool = False,
|
||||
unread: bool = False,
|
||||
shared: bool = False,
|
||||
with_tags: bool = False,
|
||||
user: User = None):
|
||||
|
@ -106,7 +107,12 @@ class BookmarkFactoryMixin:
|
|||
if with_tags:
|
||||
tag_name = f'{tag_prefix} {i}{suffix}'
|
||||
tags = [self.setup_tag(name=tag_name)]
|
||||
bookmark = self.setup_bookmark(url=url, title=title, is_archived=archived, shared=shared, tags=tags,
|
||||
bookmark = self.setup_bookmark(url=url,
|
||||
title=title,
|
||||
is_archived=archived,
|
||||
unread=unread,
|
||||
shared=shared,
|
||||
tags=tags,
|
||||
user=user)
|
||||
bookmarks.append(bookmark)
|
||||
|
||||
|
|
|
@ -13,17 +13,20 @@ class BookmarkSearchFormTest(TestCase, BookmarkFactoryMixin):
|
|||
self.assertEqual(form['sort'].initial, BookmarkSearch.SORT_ADDED_DESC)
|
||||
self.assertEqual(form['user'].initial, '')
|
||||
self.assertEqual(form['shared'].initial, '')
|
||||
self.assertEqual(form['unread'].initial, '')
|
||||
|
||||
# with params
|
||||
search = BookmarkSearch(q='search query',
|
||||
sort=BookmarkSearch.SORT_ADDED_ASC,
|
||||
user='user123',
|
||||
shared=BookmarkSearch.FILTER_SHARED_SHARED)
|
||||
shared=BookmarkSearch.FILTER_SHARED_SHARED,
|
||||
unread=BookmarkSearch.FILTER_UNREAD_YES)
|
||||
form = BookmarkSearchForm(search)
|
||||
self.assertEqual(form['q'].initial, 'search query')
|
||||
self.assertEqual(form['sort'].initial, BookmarkSearch.SORT_ADDED_ASC)
|
||||
self.assertEqual(form['user'].initial, 'user123')
|
||||
self.assertEqual(form['shared'].initial, BookmarkSearch.FILTER_SHARED_SHARED)
|
||||
self.assertEqual(form['unread'].initial, BookmarkSearch.FILTER_UNREAD_YES)
|
||||
|
||||
def test_user_options(self):
|
||||
users = [
|
||||
|
@ -57,9 +60,11 @@ class BookmarkSearchFormTest(TestCase, BookmarkFactoryMixin):
|
|||
search = BookmarkSearch(q='search query',
|
||||
sort=BookmarkSearch.SORT_ADDED_ASC,
|
||||
user='user123',
|
||||
shared=BookmarkSearch.FILTER_SHARED_SHARED)
|
||||
shared=BookmarkSearch.FILTER_SHARED_SHARED,
|
||||
unread=BookmarkSearch.FILTER_UNREAD_YES)
|
||||
form = BookmarkSearchForm(search)
|
||||
self.assertCountEqual(form.hidden_fields(), [form['q'], form['sort'], form['user'], form['shared']])
|
||||
self.assertCountEqual(form.hidden_fields(),
|
||||
[form['q'], form['sort'], form['user'], form['shared'], form['unread']])
|
||||
|
||||
# some modified params are editable fields
|
||||
search = BookmarkSearch(q='search query',
|
||||
|
|
|
@ -14,6 +14,7 @@ class BookmarkSearchModelTest(TestCase):
|
|||
self.assertEqual(search.sort, BookmarkSearch.SORT_ADDED_DESC)
|
||||
self.assertEqual(search.user, '')
|
||||
self.assertEqual(search.shared, '')
|
||||
self.assertEqual(search.unread, '')
|
||||
|
||||
# some params
|
||||
mock_request.GET = {
|
||||
|
@ -26,6 +27,7 @@ class BookmarkSearchModelTest(TestCase):
|
|||
self.assertEqual(bookmark_search.sort, BookmarkSearch.SORT_ADDED_DESC)
|
||||
self.assertEqual(bookmark_search.user, 'user123')
|
||||
self.assertEqual(bookmark_search.shared, '')
|
||||
self.assertEqual(bookmark_search.unread, '')
|
||||
|
||||
# all params
|
||||
mock_request.GET = {
|
||||
|
@ -33,6 +35,7 @@ class BookmarkSearchModelTest(TestCase):
|
|||
'user': 'user123',
|
||||
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
||||
'shared': BookmarkSearch.FILTER_SHARED_SHARED,
|
||||
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
||||
}
|
||||
|
||||
search = BookmarkSearch.from_request(mock_request)
|
||||
|
@ -40,6 +43,7 @@ class BookmarkSearchModelTest(TestCase):
|
|||
self.assertEqual(search.user, 'user123')
|
||||
self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_ASC)
|
||||
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_SHARED)
|
||||
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_YES)
|
||||
|
||||
def test_modified_params(self):
|
||||
# no params
|
||||
|
@ -58,6 +62,10 @@ class BookmarkSearchModelTest(TestCase):
|
|||
self.assertCountEqual(modified_params, ['q', 'sort'])
|
||||
|
||||
# all modified params
|
||||
bookmark_search = BookmarkSearch(q='search query', sort=BookmarkSearch.SORT_ADDED_ASC, user='user123', shared=BookmarkSearch.FILTER_SHARED_SHARED)
|
||||
bookmark_search = BookmarkSearch(q='search query',
|
||||
sort=BookmarkSearch.SORT_ADDED_ASC,
|
||||
user='user123',
|
||||
shared=BookmarkSearch.FILTER_SHARED_SHARED,
|
||||
unread=BookmarkSearch.FILTER_UNREAD_YES)
|
||||
modified_params = bookmark_search.modified_params
|
||||
self.assertCountEqual(modified_params, ['q', 'sort', 'user', 'shared'])
|
||||
self.assertCountEqual(modified_params, ['q', 'sort', 'user', 'shared', 'unread'])
|
||||
|
|
|
@ -45,12 +45,14 @@ class BookmarkSearchTagTest(TestCase, BookmarkFactoryMixin):
|
|||
self.assertNoHiddenInput(rendered_template, 'q')
|
||||
self.assertNoHiddenInput(rendered_template, 'sort')
|
||||
self.assertNoHiddenInput(rendered_template, 'shared')
|
||||
self.assertNoHiddenInput(rendered_template, 'unread')
|
||||
|
||||
# With params
|
||||
url = '/test?q=foo&user=john&sort=title_asc&shared=shared'
|
||||
url = '/test?q=foo&user=john&sort=title_asc&shared=shared&unread=yes'
|
||||
rendered_template = self.render_template(url)
|
||||
|
||||
self.assertHiddenInput(rendered_template, 'user', 'john')
|
||||
self.assertNoHiddenInput(rendered_template, 'q')
|
||||
self.assertNoHiddenInput(rendered_template, 'sort')
|
||||
self.assertNoHiddenInput(rendered_template, 'shared')
|
||||
self.assertNoHiddenInput(rendered_template, 'unread')
|
||||
|
|
|
@ -70,6 +70,25 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
|
|||
expected_status_code=status.HTTP_200_OK)
|
||||
self.assertBookmarkListEqual(response.data['results'], bookmarks)
|
||||
|
||||
def test_list_bookmarks_filter_unread(self):
|
||||
self.authenticate()
|
||||
unread_bookmarks = self.setup_numbered_bookmarks(5, unread=True)
|
||||
read_bookmarks = self.setup_numbered_bookmarks(5, unread=False)
|
||||
|
||||
# Filter off
|
||||
response = self.get(reverse('bookmarks:bookmark-list'), expected_status_code=status.HTTP_200_OK)
|
||||
self.assertBookmarkListEqual(response.data['results'], unread_bookmarks + read_bookmarks)
|
||||
|
||||
# Filter shared
|
||||
response = self.get(reverse('bookmarks:bookmark-list') + '?unread=yes',
|
||||
expected_status_code=status.HTTP_200_OK)
|
||||
self.assertBookmarkListEqual(response.data['results'], unread_bookmarks)
|
||||
|
||||
# Filter unshared
|
||||
response = self.get(reverse('bookmarks:bookmark-list') + '?unread=no',
|
||||
expected_status_code=status.HTTP_200_OK)
|
||||
self.assertBookmarkListEqual(response.data['results'], read_bookmarks)
|
||||
|
||||
def test_list_bookmarks_filter_shared(self):
|
||||
self.authenticate()
|
||||
unshared_bookmarks = self.setup_numbered_bookmarks(5)
|
||||
|
|
|
@ -394,31 +394,51 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
|
|||
self.assertCountEqual(list(query), [])
|
||||
|
||||
def test_query_bookmarks_unread_should_return_unread_bookmarks_only(self):
|
||||
unread_bookmarks = [
|
||||
self.setup_bookmark(unread=True),
|
||||
self.setup_bookmark(unread=True),
|
||||
self.setup_bookmark(unread=True),
|
||||
]
|
||||
self.setup_bookmark()
|
||||
self.setup_bookmark()
|
||||
self.setup_bookmark()
|
||||
unread_bookmarks = self.setup_numbered_bookmarks(5, unread=True)
|
||||
read_bookmarks = self.setup_numbered_bookmarks(5, unread=False)
|
||||
|
||||
# Legacy query filter
|
||||
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='!unread'))
|
||||
self.assertCountEqual(list(query), unread_bookmarks)
|
||||
|
||||
def test_query_archived_bookmarks_unread_should_return_unread_bookmarks_only(self):
|
||||
unread_bookmarks = [
|
||||
self.setup_bookmark(is_archived=True, unread=True),
|
||||
self.setup_bookmark(is_archived=True, unread=True),
|
||||
self.setup_bookmark(is_archived=True, unread=True),
|
||||
]
|
||||
self.setup_bookmark(is_archived=True)
|
||||
self.setup_bookmark(is_archived=True)
|
||||
self.setup_bookmark(is_archived=True)
|
||||
# Bookmark search filter - off
|
||||
query = queries.query_bookmarks(self.user, self.profile,
|
||||
BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_OFF))
|
||||
self.assertCountEqual(list(query), read_bookmarks + unread_bookmarks)
|
||||
|
||||
# Bookmark search filter - yes
|
||||
query = queries.query_bookmarks(self.user, self.profile,
|
||||
BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_YES))
|
||||
self.assertCountEqual(list(query), unread_bookmarks)
|
||||
|
||||
# Bookmark search filter - no
|
||||
query = queries.query_bookmarks(self.user, self.profile,
|
||||
BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_NO))
|
||||
self.assertCountEqual(list(query), read_bookmarks)
|
||||
|
||||
def test_query_archived_bookmarks_unread_should_return_unread_bookmarks_only(self):
|
||||
unread_bookmarks = self.setup_numbered_bookmarks(5, unread=True, archived=True)
|
||||
read_bookmarks = self.setup_numbered_bookmarks(5, unread=False, archived=True)
|
||||
|
||||
# Legacy query filter
|
||||
query = queries.query_archived_bookmarks(self.user, self.profile, BookmarkSearch(query='!unread'))
|
||||
self.assertCountEqual(list(query), unread_bookmarks)
|
||||
|
||||
# Bookmark search filter - off
|
||||
query = queries.query_archived_bookmarks(self.user, self.profile,
|
||||
BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_OFF))
|
||||
self.assertCountEqual(list(query), read_bookmarks + unread_bookmarks)
|
||||
|
||||
# Bookmark search filter - yes
|
||||
query = queries.query_archived_bookmarks(self.user, self.profile,
|
||||
BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_YES))
|
||||
self.assertCountEqual(list(query), unread_bookmarks)
|
||||
|
||||
# Bookmark search filter - no
|
||||
query = queries.query_archived_bookmarks(self.user, self.profile,
|
||||
BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_NO))
|
||||
self.assertCountEqual(list(query), read_bookmarks)
|
||||
|
||||
def test_query_bookmarks_filter_shared(self):
|
||||
unshared_bookmarks = self.setup_numbered_bookmarks(5)
|
||||
shared_bookmarks = self.setup_numbered_bookmarks(5, shared=True)
|
||||
|
@ -681,6 +701,31 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
|
|||
BookmarkSearch(query=f'!untagged #{tag.name}'))
|
||||
self.assertCountEqual(list(query), [])
|
||||
|
||||
def test_query_bookmark_tags_filter_unread(self):
|
||||
unread_bookmarks = self.setup_numbered_bookmarks(5, unread=True, with_tags=True)
|
||||
read_bookmarks = self.setup_numbered_bookmarks(5, unread=False, with_tags=True)
|
||||
unread_tags = self.get_tags_from_bookmarks(unread_bookmarks)
|
||||
read_tags = self.get_tags_from_bookmarks(read_bookmarks)
|
||||
|
||||
# Legacy query filter
|
||||
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='!unread'))
|
||||
self.assertCountEqual(list(query), unread_tags)
|
||||
|
||||
# Bookmark search filter - off
|
||||
query = queries.query_bookmark_tags(self.user, self.profile,
|
||||
BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_OFF))
|
||||
self.assertCountEqual(list(query), read_tags + unread_tags)
|
||||
|
||||
# Bookmark search filter - yes
|
||||
query = queries.query_bookmark_tags(self.user, self.profile,
|
||||
BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_YES))
|
||||
self.assertCountEqual(list(query), unread_tags)
|
||||
|
||||
# Bookmark search filter - no
|
||||
query = queries.query_bookmark_tags(self.user, self.profile,
|
||||
BookmarkSearch(unread=BookmarkSearch.FILTER_UNREAD_NO))
|
||||
self.assertCountEqual(list(query), read_tags)
|
||||
|
||||
def test_query_bookmark_tags_filter_shared(self):
|
||||
unshared_bookmarks = self.setup_numbered_bookmarks(5, with_tags=True)
|
||||
shared_bookmarks = self.setup_numbered_bookmarks(5, with_tags=True, shared=True)
|
||||
|
|
|
@ -79,12 +79,14 @@ class UserSelectTagTest(TestCase, BookmarkFactoryMixin):
|
|||
self.assertNoHiddenInput(rendered_template, 'q')
|
||||
self.assertNoHiddenInput(rendered_template, 'sort')
|
||||
self.assertNoHiddenInput(rendered_template, 'shared')
|
||||
self.assertNoHiddenInput(rendered_template, 'unread')
|
||||
|
||||
# With params
|
||||
url = '/test?q=foo&user=john&sort=title_asc&shared=shared'
|
||||
url = '/test?q=foo&user=john&sort=title_asc&shared=shared&unread=yes'
|
||||
rendered_template = self.render_template(url)
|
||||
|
||||
self.assertNoHiddenInput(rendered_template, 'user')
|
||||
self.assertHiddenInput(rendered_template, 'q', 'foo')
|
||||
self.assertHiddenInput(rendered_template, 'sort', 'title_asc')
|
||||
self.assertHiddenInput(rendered_template, 'shared', 'shared')
|
||||
self.assertHiddenInput(rendered_template, 'unread', 'yes')
|
||||
|
|
Loading…
Reference in a new issue