Allow saving search preferences (#540)

* Add indicator for modified filters

* Rename shared filter values

* Add update search preferences handler

* Separate search and preferences forms

* Properly initialize bookmark search from get or post

* Add tests for applying search preferences

* Implement saving search preferences

* Remove bookmark search query alias

* Use search preferences as default

* Only show save button for authenticated users

* Only show modified indicator if preferences are modified

* Fix overriding search preferences

* Add missing migration
This commit is contained in:
Sascha Ißbrücker
2023-10-01 21:22:44 +02:00
committed by GitHub
parent 4a2642f16c
commit 41f79e35a0
22 changed files with 1094 additions and 442 deletions

View File

@@ -34,7 +34,7 @@ class BookmarkViewSet(viewsets.GenericViewSet,
user = self.request.user user = self.request.user
# For list action, use query set that applies search and tag projections # For list action, use query set that applies search and tag projections
if self.action == 'list': if self.action == 'list':
search = BookmarkSearch.from_request(self.request) search = BookmarkSearch.from_request(self.request.GET)
return queries.query_bookmarks(user, user.profile, search) return queries.query_bookmarks(user, user.profile, search)
# For single entity actions use default query set without projections # For single entity actions use default query set without projections
@@ -46,7 +46,7 @@ class BookmarkViewSet(viewsets.GenericViewSet,
@action(methods=['get'], detail=False) @action(methods=['get'], detail=False)
def archived(self, request): def archived(self, request):
user = request.user user = request.user
search = BookmarkSearch.from_request(request) search = BookmarkSearch.from_request(request.GET)
query_set = queries.query_archived_bookmarks(user, user.profile, search) query_set = queries.query_archived_bookmarks(user, user.profile, search)
page = self.paginate_queryset(query_set) page = self.paginate_queryset(query_set)
serializer = self.get_serializer_class() serializer = self.get_serializer_class()
@@ -55,7 +55,7 @@ class BookmarkViewSet(viewsets.GenericViewSet,
@action(methods=['get'], detail=False) @action(methods=['get'], detail=False)
def shared(self, request): def shared(self, request):
search = BookmarkSearch.from_request(request) search = BookmarkSearch.from_request(request.GET)
user = User.objects.filter(username=search.user).first() user = User.objects.filter(username=search.user).first()
public_only = not request.user.is_authenticated public_only = not request.user.is_authenticated
query_set = queries.query_shared_bookmarks(user, request.user_profile, search, public_only) query_set = queries.query_shared_bookmarks(user, request.user_profile, search, public_only)

View File

@@ -17,7 +17,7 @@ class FeedContext:
class BaseBookmarksFeed(Feed): class BaseBookmarksFeed(Feed):
def get_object(self, request, feed_key: str): def get_object(self, request, feed_key: str):
feed_token = FeedToken.objects.get(key__exact=feed_key) feed_token = FeedToken.objects.get(key__exact=feed_key)
search = BookmarkSearch(query=request.GET.get('q', '')) search = BookmarkSearch(q=request.GET.get('q', ''))
query_set = queries.query_bookmarks(feed_token.user, feed_token.user.profile, search) query_set = queries.query_bookmarks(feed_token.user, feed_token.user.profile, search)
return FeedContext(feed_token, query_set) return FeedContext(feed_token, query_set)

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.1.9 on 2023-09-30 10:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bookmarks', '0024_userprofile_enable_public_sharing'),
]
operations = [
migrations.AddField(
model_name='userprofile',
name='search_preferences',
field=models.JSONField(default=dict),
),
]

View File

@@ -5,10 +5,10 @@ from typing import List
from django import forms from django import forms
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.handlers.wsgi import WSGIRequest
from django.db import models from django.db import models
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.http import QueryDict
from bookmarks.utils import unique from bookmarks.utils import unique
from bookmarks.validators import BookmarkURLValidator from bookmarks.validators import BookmarkURLValidator
@@ -130,15 +130,16 @@ class BookmarkSearch:
SORT_TITLE_ASC = 'title_asc' SORT_TITLE_ASC = 'title_asc'
SORT_TITLE_DESC = 'title_desc' SORT_TITLE_DESC = 'title_desc'
FILTER_SHARED_OFF = '' FILTER_SHARED_OFF = 'off'
FILTER_SHARED_SHARED = 'shared' FILTER_SHARED_SHARED = 'yes'
FILTER_SHARED_UNSHARED = 'unshared' FILTER_SHARED_UNSHARED = 'no'
FILTER_UNREAD_OFF = '' FILTER_UNREAD_OFF = 'off'
FILTER_UNREAD_YES = 'yes' FILTER_UNREAD_YES = 'yes'
FILTER_UNREAD_NO = 'no' FILTER_UNREAD_NO = 'no'
params = ['q', 'user', 'sort', 'shared', 'unread'] params = ['q', 'user', 'sort', 'shared', 'unread']
preferences = ['sort', 'shared', 'unread']
defaults = { defaults = {
'q': '', 'q': '',
'user': '', 'user': '',
@@ -148,43 +149,59 @@ class BookmarkSearch:
} }
def __init__(self, def __init__(self,
q: str = defaults['q'], q: str = None,
query: str = defaults['q'], # alias for q user: str = None,
user: str = defaults['user'], sort: str = None,
sort: str = defaults['sort'], shared: str = None,
shared: str = defaults['shared'], unread: str = None,
unread: str = defaults['unread']): preferences: dict = None):
self.q = q or query if not preferences:
self.user = user preferences = {}
self.sort = sort self.defaults = {**BookmarkSearch.defaults, **preferences}
self.shared = shared
self.unread = unread
@property self.q = q or self.defaults['q']
def query(self): self.user = user or self.defaults['user']
return self.q self.sort = sort or self.defaults['sort']
self.shared = shared or self.defaults['shared']
self.unread = unread or self.defaults['unread']
def is_modified(self, param): def is_modified(self, param):
value = self.__dict__[param] value = self.__dict__[param]
return value and value != BookmarkSearch.defaults[param] return value != self.defaults[param]
@property @property
def modified_params(self): def modified_params(self):
return [field for field in self.params if self.is_modified(field)] return [field for field in self.params if self.is_modified(field)]
@property
def modified_preferences(self):
return [preference for preference in self.preferences if self.is_modified(preference)]
@property
def has_modifications(self):
return len(self.modified_params) > 0
@property
def has_modified_preferences(self):
return len(self.modified_preferences) > 0
@property @property
def query_params(self): def query_params(self):
return {param: self.__dict__[param] for param in self.modified_params} return {param: self.__dict__[param] for param in self.modified_params}
@property
def preferences_dict(self):
return {preference: self.__dict__[preference] for preference in self.preferences}
@staticmethod @staticmethod
def from_request(request: WSGIRequest): def from_request(query_dict: QueryDict, preferences: dict = None):
initial_values = {} initial_values = {}
for param in BookmarkSearch.params: for param in BookmarkSearch.params:
value = request.GET.get(param) value = query_dict.get(param)
if value: if value:
initial_values[param] = value initial_values[param] = value
return BookmarkSearch(**initial_values) return BookmarkSearch(**initial_values, preferences=preferences)
class BookmarkSearchForm(forms.Form): class BookmarkSearchForm(forms.Form):
@@ -214,6 +231,7 @@ class BookmarkSearchForm(forms.Form):
def __init__(self, search: BookmarkSearch, editable_fields: List[str] = None, users: List[User] = None): def __init__(self, search: BookmarkSearch, editable_fields: List[str] = None, users: List[User] = None):
super().__init__() super().__init__()
editable_fields = editable_fields or [] editable_fields = editable_fields or []
self.editable_fields = editable_fields
# set choices for user field if users are provided # set choices for user field if users are provided
if users: if users:
@@ -282,6 +300,7 @@ class UserProfile(models.Model):
enable_favicons = models.BooleanField(default=False, null=False) enable_favicons = models.BooleanField(default=False, null=False)
display_url = models.BooleanField(default=False, null=False) display_url = models.BooleanField(default=False, null=False)
permanent_notes = models.BooleanField(default=False, null=False) permanent_notes = models.BooleanField(default=False, null=False)
search_preferences = models.JSONField(default=dict, null=False)
class UserProfileForm(forms.ModelForm): class UserProfileForm(forms.ModelForm):

View File

@@ -37,7 +37,7 @@ def _base_bookmarks_query(user: Optional[User], profile: UserProfile, search: Bo
query_set = query_set.filter(owner=user) query_set = query_set.filter(owner=user)
# Split query into search terms and tags # Split query into search terms and tags
query = parse_query_string(search.query) query = parse_query_string(search.q)
# Filter for search terms and tags # Filter for search terms and tags
for term in query['search_terms']: for term in query['search_terms']:

View File

@@ -13,7 +13,7 @@
} }
} }
.bookmarks-page #search { .bookmarks-page .search-container {
flex: 1 1 0; flex: 1 1 0;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;

View File

@@ -21,6 +21,7 @@
@import "../../node_modules/spectre.css/src/media"; @import "../../node_modules/spectre.css/src/media";
// Components // Components
@import "../../node_modules/spectre.css/src/badges";
@import "../../node_modules/spectre.css/src/dropdowns"; @import "../../node_modules/spectre.css/src/dropdowns";
@import "../../node_modules/spectre.css/src/empty"; @import "../../node_modules/spectre.css/src/empty";
@import "../../node_modules/spectre.css/src/menus"; @import "../../node_modules/spectre.css/src/menus";

View File

@@ -1,13 +1,16 @@
{% load widget_tweaks %} {% load widget_tweaks %}
<form id="search" action="" method="get" role="search"> <div class="search-container">
<div class="input-group"> <form id="search" class="input-group" action="" method="get" role="search">
<input type="search" class="form-input" name="q" placeholder="Search for words or #tags" <input type="search" class="form-input" name="q" placeholder="Search for words or #tags"
value="{{ search.query }}"> value="{{ search.q }}">
<input type="submit" value="Search" class="btn input-group-btn"> <input type="submit" value="Search" class="btn input-group-btn">
</div> {% for hidden_field in search_form.hidden_fields %}
{{ hidden_field }}
{% endfor %}
</form>
<div class="search-options dropdown dropdown-right"> <div class="search-options dropdown dropdown-right">
<button type="button" class="btn dropdown-toggle"> <button type="button" class="btn dropdown-toggle{% if search.has_modified_preferences %} badge{% endif %}">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" stroke-width="2" <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
@@ -23,13 +26,19 @@
</svg> </svg>
</button> </button>
<div class="menu text-sm" tabindex="0"> <div class="menu text-sm" tabindex="0">
<form id="search_preferences" action="" method="post">
{% csrf_token %}
{% if 'sort' in preferences_form.editable_fields %}
<div class="form-group"> <div class="form-group">
<label for="{{ form.sort.id_for_label }}" class="form-label">Sort by</label> <label for="{{ preferences_form.sort.id_for_label }}"
{{ form.sort|add_class:"form-select select-sm" }} class="form-label{% if 'sort' in search.modified_params %} text-bold{% endif %}">Sort by</label>
{{ preferences_form.sort|add_class:"form-select select-sm" }}
</div> </div>
{% endif %}
{% if 'shared' in preferences_form.editable_fields %}
<div class="form-group radio-group"> <div class="form-group radio-group">
<div class="form-label">Shared filter</div> <div class="form-label{% if 'shared' in search.modified_params %} text-bold{% endif %}">Shared filter</div>
{% for radio in form.shared %} {% for radio in preferences_form.shared %}
<label for="{{ radio.id_for_label }}" class="form-radio form-inline"> <label for="{{ radio.id_for_label }}" class="form-radio form-inline">
{{ radio.tag }} {{ radio.tag }}
<i class="form-icon"></i> <i class="form-icon"></i>
@@ -37,9 +46,11 @@
</label> </label>
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
{% if 'unread' in preferences_form.editable_fields %}
<div class="form-group radio-group"> <div class="form-group radio-group">
<div class="form-label">Unread filter</div> <div class="form-label{% if 'unread' in search.modified_params %} text-bold{% endif %}">Unread filter</div>
{% for radio in form.unread %} {% for radio in preferences_form.unread %}
<label for="{{ radio.id_for_label }}" class="form-radio form-inline"> <label for="{{ radio.id_for_label }}" class="form-radio form-inline">
{{ radio.tag }} {{ radio.tag }}
<i class="form-icon"></i> <i class="form-icon"></i>
@@ -47,16 +58,22 @@
</label> </label>
{% endfor %} {% endfor %}
</div> </div>
{% endif %}
<div class="actions"> <div class="actions">
<button type="submit" class="btn btn-sm btn-primary">Apply</button> <button type="submit" class="btn btn-sm btn-primary" name="apply">Apply</button>
</div> {% if request.user.is_authenticated %}
</div> <button type="submit" class="btn btn-sm" name="save">Save as default</button>
{% endif %}
</div> </div>
{% for hidden_field in form.hidden_fields %} {% for hidden_field in preferences_form.hidden_fields %}
{{ hidden_field }} {{ hidden_field }}
{% endfor %} {% endfor %}
</form> </form>
</div>
</div>
</div>
{# Replace search input with auto-complete component #} {# Replace search input with auto-complete component #}
<script type="application/javascript"> <script type="application/javascript">
@@ -65,7 +82,7 @@
const currentTags = currentTagsString.split(' '); const currentTags = currentTagsString.split(' ');
const uniqueTags = [...new Set(currentTags)] const uniqueTags = [...new Set(currentTags)]
const search = { const search = {
q: '{{ search.query }}', q: '{{ search.q }}',
user: '{{ search.user }}', user: '{{ search.user }}',
shared: '{{ search.shared }}', shared: '{{ search.shared }}',
unread: '{{ search.unread }}', unread: '{{ search.unread }}',
@@ -78,7 +95,7 @@
props: { props: {
name: 'q', name: 'q',
placeholder: 'Search for words or #tags', placeholder: 'Search for words or #tags',
value: '{{ search.query|safe }}', value: '{{ search.q|safe }}',
tags: uniqueTags, tags: uniqueTags,
mode: '{{ mode }}', mode: '{{ mode }}',
linkTarget: '{{ request.user_profile.bookmark_link_target }}', linkTarget: '{{ request.user_profile.bookmark_link_target }}',

View File

@@ -22,11 +22,17 @@ def bookmark_form(context, form: BookmarkForm, cancel_url: str, bookmark_id: int
def bookmark_search(context, search: BookmarkSearch, tags: [Tag], mode: str = ''): def bookmark_search(context, search: BookmarkSearch, tags: [Tag], mode: str = ''):
tag_names = [tag.name for tag in tags] tag_names = [tag.name for tag in tags]
tags_string = build_tag_string(tag_names, ' ') tags_string = build_tag_string(tag_names, ' ')
form = BookmarkSearchForm(search, editable_fields=['q', 'sort', 'shared', 'unread']) search_form = BookmarkSearchForm(search, editable_fields=['q'])
if mode == 'shared':
preferences_form = BookmarkSearchForm(search, editable_fields=['sort'])
else:
preferences_form = BookmarkSearchForm(search, editable_fields=['sort', 'shared', 'unread'])
return { return {
'request': context['request'], 'request': context['request'],
'search': search, 'search': search,
'form': form, 'search_form': search_form,
'preferences_form': preferences_form,
'tags_string': tags_string, 'tags_string': tags_string,
'mode': mode, 'mode': mode,
} }

View File

@@ -106,7 +106,7 @@ class BookmarkFactoryMixin:
tags = [] tags = []
if with_tags: if with_tags:
tag_name = f'{tag_prefix} {i}{suffix}' tag_name = f'{tag_prefix} {i}{suffix}'
tags = [self.setup_tag(name=tag_name)] tags = [self.setup_tag(name=tag_name, user=user)]
bookmark = self.setup_bookmark(url=url, bookmark = self.setup_bookmark(url=url,
title=title, title=title,
is_archived=archived, is_archived=archived,
@@ -139,6 +139,12 @@ class BookmarkFactoryMixin:
user.profile.save() user.profile.save()
return user return user
def get_tags_from_bookmarks(self, bookmarks: [Bookmark]):
all_tags = []
for bookmark in bookmarks:
all_tags = all_tags + list(bookmark.tags.all())
return all_tags
def get_random_string(self, length: int = 32): def get_random_string(self, length: int = 32):
return get_random_string(length=length) return get_random_string(length=length)

View File

@@ -440,21 +440,6 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived) self.assertTrue(Bookmark.objects.get(id=bookmark2.id).is_archived)
self.assertTrue(Bookmark.objects.get(id=bookmark3.id).is_archived) self.assertTrue(Bookmark.objects.get(id=bookmark3.id).is_archived)
def test_bulk_select_across_respects_query(self):
self.setup_numbered_bookmarks(3, prefix='foo')
self.setup_numbered_bookmarks(3, prefix='bar')
self.assertEqual(3, Bookmark.objects.filter(title__startswith='foo').count())
self.client.post(reverse('bookmarks:index.action') + '?q=foo', {
'bulk_action': ['bulk_delete'],
'bulk_execute': [''],
'bulk_select_across': ['on'],
})
self.assertEqual(0, Bookmark.objects.filter(title__startswith='foo').count())
self.assertEqual(3, Bookmark.objects.filter(title__startswith='bar').count())
def test_bulk_select_across_ignores_page(self): def test_bulk_select_across_ignores_page(self):
self.setup_numbered_bookmarks(100) self.setup_numbered_bookmarks(100)
@@ -493,6 +478,21 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertIsNone(Bookmark.objects.filter(title='Bookmark 2').first()) self.assertIsNone(Bookmark.objects.filter(title='Bookmark 2').first())
self.assertIsNone(Bookmark.objects.filter(title='Bookmark 3').first()) self.assertIsNone(Bookmark.objects.filter(title='Bookmark 3').first())
def test_index_action_bulk_select_across_respects_query(self):
self.setup_numbered_bookmarks(3, prefix='foo')
self.setup_numbered_bookmarks(3, prefix='bar')
self.assertEqual(3, Bookmark.objects.filter(title__startswith='foo').count())
self.client.post(reverse('bookmarks:index.action') + '?q=foo', {
'bulk_action': ['bulk_delete'],
'bulk_execute': [''],
'bulk_select_across': ['on'],
})
self.assertEqual(0, Bookmark.objects.filter(title__startswith='foo').count())
self.assertEqual(3, Bookmark.objects.filter(title__startswith='bar').count())
def test_archived_action_bulk_select_across_only_affects_archived_bookmarks(self): def test_archived_action_bulk_select_across_only_affects_archived_bookmarks(self):
self.setup_bulk_edit_scope_test_data() self.setup_bulk_edit_scope_test_data()
@@ -511,6 +511,21 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertIsNone(Bookmark.objects.filter(title='Archived Bookmark 2').first()) self.assertIsNone(Bookmark.objects.filter(title='Archived Bookmark 2').first())
self.assertIsNone(Bookmark.objects.filter(title='Archived Bookmark 3').first()) self.assertIsNone(Bookmark.objects.filter(title='Archived Bookmark 3').first())
def test_archived_action_bulk_select_across_respects_query(self):
self.setup_numbered_bookmarks(3, prefix='foo', archived=True)
self.setup_numbered_bookmarks(3, prefix='bar', archived=True)
self.assertEqual(3, Bookmark.objects.filter(title__startswith='foo').count())
self.client.post(reverse('bookmarks:archived.action') + '?q=foo', {
'bulk_action': ['bulk_delete'],
'bulk_execute': [''],
'bulk_select_across': ['on'],
})
self.assertEqual(0, Bookmark.objects.filter(title__startswith='foo').count())
self.assertEqual(3, Bookmark.objects.filter(title__startswith='bar').count())
def test_shared_action_bulk_select_across_not_supported(self): def test_shared_action_bulk_select_across_not_supported(self):
self.setup_bulk_edit_scope_test_data() self.setup_bulk_edit_scope_test_data()

View File

@@ -5,7 +5,7 @@ from django.contrib.auth.models import User
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from bookmarks.models import Bookmark, Tag, UserProfile from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin, collapse_whitespace from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin, collapse_whitespace
@@ -16,38 +16,51 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
self.client.force_login(user) self.client.force_login(user)
def assertVisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'): def assertVisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'):
html = response.content.decode() soup = self.make_soup(response.content.decode())
self.assertContains(response, '<li ld-bookmark-item>', count=len(bookmarks)) bookmark_list = soup.select_one(f'ul.bookmark-list[data-bookmarks-total="{len(bookmarks)}"]')
self.assertIsNotNone(bookmark_list)
bookmark_items = bookmark_list.select('li[ld-bookmark-item]')
self.assertEqual(len(bookmark_items), len(bookmarks))
for bookmark in bookmarks: for bookmark in bookmarks:
self.assertInHTML( bookmark_item = bookmark_list.select_one(
f'<a href="{bookmark.url}" target="{link_target}" rel="noopener">{bookmark.resolved_title}</a>', f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]')
html self.assertIsNotNone(bookmark_item)
)
def assertInvisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'): def assertInvisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'):
html = response.content.decode() soup = self.make_soup(response.content.decode())
for bookmark in bookmarks: for bookmark in bookmarks:
self.assertInHTML( bookmark_item = soup.select_one(
f'<a href="{bookmark.url}" target="{link_target}" rel="noopener">{bookmark.resolved_title}</a>', f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]')
html, self.assertIsNone(bookmark_item)
count=0
)
def assertVisibleTags(self, response, tags: List[Tag]): def assertVisibleTags(self, response, tags: List[Tag]):
self.assertContains(response, 'data-is-tag-item', count=len(tags)) soup = self.make_soup(response.content.decode())
tag_cloud = soup.select_one('div.tag-cloud')
self.assertIsNotNone(tag_cloud)
tag_items = tag_cloud.select('a[data-is-tag-item]')
self.assertEqual(len(tag_items), len(tags))
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
for tag in tags: for tag in tags:
self.assertContains(response, tag.name) self.assertTrue(tag.name in tag_item_names)
def assertInvisibleTags(self, response, tags: List[Tag]): def assertInvisibleTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
tag_items = soup.select('a[data-is-tag-item]')
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
for tag in tags: for tag in tags:
self.assertNotContains(response, tag.name) self.assertFalse(tag.name in tag_item_names)
def assertSelectedTags(self, response, tags: List[Tag]): def assertSelectedTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode()) soup = self.make_soup(response.content.decode())
selected_tags = soup.select('p.selected-tags')[0] selected_tags = soup.select_one('p.selected-tags')
self.assertIsNotNone(selected_tags) self.assertIsNotNone(selected_tags)
tag_list = selected_tags.select('a') tag_list = selected_tags.select('a')
@@ -73,67 +86,36 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def test_should_list_archived_and_user_owned_bookmarks(self): def test_should_list_archived_and_user_owned_bookmarks(self):
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
visible_bookmarks = [ visible_bookmarks = self.setup_numbered_bookmarks(3, archived=True)
self.setup_bookmark(is_archived=True),
self.setup_bookmark(is_archived=True),
self.setup_bookmark(is_archived=True)
]
invisible_bookmarks = [ invisible_bookmarks = [
self.setup_bookmark(is_archived=False), self.setup_bookmark(is_archived=False),
self.setup_bookmark(is_archived=True, user=other_user), self.setup_bookmark(is_archived=True, user=other_user),
] ]
response = self.client.get(reverse('bookmarks:archived')) response = self.client.get(reverse('bookmarks:archived'))
html = collapse_whitespace(response.content.decode())
# Should render list
self.assertIn('<ul class="bookmark-list" data-bookmarks-total="3">', html)
self.assertVisibleBookmarks(response, visible_bookmarks) self.assertVisibleBookmarks(response, visible_bookmarks)
self.assertInvisibleBookmarks(response, invisible_bookmarks) self.assertInvisibleBookmarks(response, invisible_bookmarks)
def test_should_list_bookmarks_matching_query(self): def test_should_list_bookmarks_matching_query(self):
visible_bookmarks = [ visible_bookmarks = self.setup_numbered_bookmarks(3, prefix='foo', archived=True)
self.setup_bookmark(is_archived=True, title='searchvalue'), invisible_bookmarks = self.setup_numbered_bookmarks(3, prefix='bar', archived=True)
self.setup_bookmark(is_archived=True, title='searchvalue'),
self.setup_bookmark(is_archived=True, title='searchvalue')
]
invisible_bookmarks = [
self.setup_bookmark(is_archived=True),
self.setup_bookmark(is_archived=True),
self.setup_bookmark(is_archived=True)
]
response = self.client.get(reverse('bookmarks:archived') + '?q=searchvalue') response = self.client.get(reverse('bookmarks:archived') + '?q=foo')
html = collapse_whitespace(response.content.decode()) html = collapse_whitespace(response.content.decode())
# Should render list
self.assertIn('<ul class="bookmark-list" data-bookmarks-total="3">', html)
self.assertVisibleBookmarks(response, visible_bookmarks) self.assertVisibleBookmarks(response, visible_bookmarks)
self.assertInvisibleBookmarks(response, invisible_bookmarks) self.assertInvisibleBookmarks(response, invisible_bookmarks)
def test_should_list_tags_for_archived_and_user_owned_bookmarks(self): def test_should_list_tags_for_archived_and_user_owned_bookmarks(self):
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
visible_tags = [ visible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=True)
self.setup_tag(), unarchived_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=False, tag_prefix='unarchived')
self.setup_tag(), other_user_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=True, user=other_user,
self.setup_tag(), tag_prefix='otheruser')
]
invisible_tags = [
self.setup_tag(), # unused tag
self.setup_tag(), # used in archived bookmark
self.setup_tag(user=other_user), # belongs to other user
]
# Assign tags to some bookmarks with duplicates visible_tags = self.get_tags_from_bookmarks(visible_bookmarks)
self.setup_bookmark(is_archived=True, tags=[visible_tags[0]]) invisible_tags = self.get_tags_from_bookmarks(unarchived_bookmarks + other_user_bookmarks)
self.setup_bookmark(is_archived=True, tags=[visible_tags[0]])
self.setup_bookmark(is_archived=True, tags=[visible_tags[1]])
self.setup_bookmark(is_archived=True, tags=[visible_tags[1]])
self.setup_bookmark(is_archived=True, tags=[visible_tags[2]])
self.setup_bookmark(is_archived=True, tags=[visible_tags[2]])
self.setup_bookmark(is_archived=False, tags=[invisible_tags[1]])
self.setup_bookmark(is_archived=True, tags=[invisible_tags[2]], user=other_user)
response = self.client.get(reverse('bookmarks:archived')) response = self.client.get(reverse('bookmarks:archived'))
@@ -141,29 +123,40 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
self.assertInvisibleTags(response, invisible_tags) self.assertInvisibleTags(response, invisible_tags)
def test_should_list_tags_for_bookmarks_matching_query(self): def test_should_list_tags_for_bookmarks_matching_query(self):
visible_tags = [ visible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=True, prefix='foo',
self.setup_tag(), tag_prefix='foo')
self.setup_tag(), invisible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=True, prefix='bar',
self.setup_tag(), tag_prefix='bar')
]
invisible_tags = [
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
]
self.setup_bookmark(is_archived=True, tags=[visible_tags[0]], title='searchvalue') visible_tags = self.get_tags_from_bookmarks(visible_bookmarks)
self.setup_bookmark(is_archived=True, tags=[visible_tags[1]], title='searchvalue') invisible_tags = self.get_tags_from_bookmarks(invisible_bookmarks)
self.setup_bookmark(is_archived=True, tags=[visible_tags[2]], title='searchvalue')
self.setup_bookmark(is_archived=True, tags=[invisible_tags[0]])
self.setup_bookmark(is_archived=True, tags=[invisible_tags[1]])
self.setup_bookmark(is_archived=True, tags=[invisible_tags[2]])
response = self.client.get(reverse('bookmarks:archived') + '?q=searchvalue') response = self.client.get(reverse('bookmarks:archived') + '?q=foo')
self.assertVisibleTags(response, visible_tags) self.assertVisibleTags(response, visible_tags)
self.assertInvisibleTags(response, invisible_tags) self.assertInvisibleTags(response, invisible_tags)
def test_should_list_bookmarks_and_tags_for_search_preferences(self):
user_profile = self.user.profile
user_profile.search_preferences = {
'unread': BookmarkSearch.FILTER_UNREAD_YES,
}
user_profile.save()
unread_bookmarks = self.setup_numbered_bookmarks(3, archived=True, unread=True, with_tags=True, prefix='unread',
tag_prefix='unread')
read_bookmarks = self.setup_numbered_bookmarks(3, archived=True, unread=False, with_tags=True, prefix='read',
tag_prefix='read')
unread_tags = self.get_tags_from_bookmarks(unread_bookmarks)
read_tags = self.get_tags_from_bookmarks(read_bookmarks)
response = self.client.get(reverse('bookmarks:archived'))
self.assertVisibleBookmarks(response, unread_bookmarks)
self.assertInvisibleBookmarks(response, read_bookmarks)
self.assertVisibleTags(response, unread_tags)
self.assertInvisibleTags(response, read_tags)
def test_should_display_selected_tags_from_query(self): def test_should_display_selected_tags_from_query(self):
tags = [ tags = [
self.setup_tag(), self.setup_tag(),
@@ -210,11 +203,7 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
self.assertSelectedTags(response, [tags[0], tags[1]]) self.assertSelectedTags(response, [tags[0], tags[1]])
def test_should_open_bookmarks_in_new_page_by_default(self): def test_should_open_bookmarks_in_new_page_by_default(self):
visible_bookmarks = [ visible_bookmarks = self.setup_numbered_bookmarks(3, archived=True)
self.setup_bookmark(is_archived=True),
self.setup_bookmark(is_archived=True),
self.setup_bookmark(is_archived=True)
]
response = self.client.get(reverse('bookmarks:archived')) response = self.client.get(reverse('bookmarks:archived'))
@@ -225,11 +214,7 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
user.profile.bookmark_link_target = UserProfile.BOOKMARK_LINK_TARGET_SELF user.profile.bookmark_link_target = UserProfile.BOOKMARK_LINK_TARGET_SELF
user.profile.save() user.profile.save()
visible_bookmarks = [ visible_bookmarks = self.setup_numbered_bookmarks(3, archived=True)
self.setup_bookmark(is_archived=True),
self.setup_bookmark(is_archived=True),
self.setup_bookmark(is_archived=True)
]
response = self.client.get(reverse('bookmarks:archived')) response = self.client.get(reverse('bookmarks:archived'))
@@ -328,6 +313,106 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
</select> </select>
''', html) ''', html)
def test_apply_search_preferences(self):
# no params
response = self.client.post(reverse('bookmarks:archived'))
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse('bookmarks:archived'))
# some params
response = self.client.post(reverse('bookmarks:archived'), {
'q': 'foo',
'sort': BookmarkSearch.SORT_TITLE_ASC,
})
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse('bookmarks:archived') + '?q=foo&sort=title_asc')
# params with default value are removed
response = self.client.post(reverse('bookmarks:archived'), {
'q': 'foo',
'user': '',
'sort': BookmarkSearch.SORT_ADDED_DESC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse('bookmarks:archived') + '?q=foo&unread=yes')
# page is removed
response = self.client.post(reverse('bookmarks:archived'), {
'q': 'foo',
'page': '2',
'sort': BookmarkSearch.SORT_TITLE_ASC,
})
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse('bookmarks:archived') + '?q=foo&sort=title_asc')
def test_save_search_preferences(self):
user_profile = self.user.profile
# no params
self.client.post(reverse('bookmarks:archived'), {
'save': '',
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_ADDED_DESC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
})
# with param
self.client.post(reverse('bookmarks:archived'), {
'save': '',
'sort': BookmarkSearch.SORT_TITLE_ASC,
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
})
# add a param
self.client.post(reverse('bookmarks:archived'), {
'save': '',
'sort': BookmarkSearch.SORT_TITLE_ASC,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
# remove a param
self.client.post(reverse('bookmarks:archived'), {
'save': '',
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_ADDED_DESC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
# ignores non-preferences
self.client.post(reverse('bookmarks:archived'), {
'save': '',
'q': 'foo',
'user': 'john',
'page': '3',
'sort': BookmarkSearch.SORT_TITLE_ASC,
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
})
def test_url_encode_bookmark_actions_url(self): def test_url_encode_bookmark_actions_url(self):
url = reverse('bookmarks:archived') + '?q=%23foo' url = reverse('bookmarks:archived') + '?q=%23foo'
response = self.client.get(url) response = self.client.get(url)

View File

@@ -5,7 +5,7 @@ from django.contrib.auth.models import User
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from bookmarks.models import Bookmark, Tag, UserProfile from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin, collapse_whitespace from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin, collapse_whitespace
@@ -16,38 +16,51 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
self.client.force_login(user) self.client.force_login(user)
def assertVisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'): def assertVisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'):
html = response.content.decode() soup = self.make_soup(response.content.decode())
self.assertContains(response, '<li ld-bookmark-item>', count=len(bookmarks)) bookmark_list = soup.select_one(f'ul.bookmark-list[data-bookmarks-total="{len(bookmarks)}"]')
self.assertIsNotNone(bookmark_list)
bookmark_items = bookmark_list.select('li[ld-bookmark-item]')
self.assertEqual(len(bookmark_items), len(bookmarks))
for bookmark in bookmarks: for bookmark in bookmarks:
self.assertInHTML( bookmark_item = bookmark_list.select_one(
f'<a href="{bookmark.url}" target="{link_target}" rel="noopener">{bookmark.resolved_title}</a>', f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]')
html self.assertIsNotNone(bookmark_item)
)
def assertInvisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'): def assertInvisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'):
html = response.content.decode() soup = self.make_soup(response.content.decode())
for bookmark in bookmarks: for bookmark in bookmarks:
self.assertInHTML( bookmark_item = soup.select_one(
f'<a href="{bookmark.url}" target="{link_target}" rel="noopener">{bookmark.resolved_title}</a>', f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]')
html, self.assertIsNone(bookmark_item)
count=0
)
def assertVisibleTags(self, response, tags: List[Tag]): def assertVisibleTags(self, response, tags: List[Tag]):
self.assertContains(response, 'data-is-tag-item', count=len(tags)) soup = self.make_soup(response.content.decode())
tag_cloud = soup.select_one('div.tag-cloud')
self.assertIsNotNone(tag_cloud)
tag_items = tag_cloud.select('a[data-is-tag-item]')
self.assertEqual(len(tag_items), len(tags))
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
for tag in tags: for tag in tags:
self.assertContains(response, tag.name) self.assertTrue(tag.name in tag_item_names)
def assertInvisibleTags(self, response, tags: List[Tag]): def assertInvisibleTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
tag_items = soup.select('a[data-is-tag-item]')
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
for tag in tags: for tag in tags:
self.assertNotContains(response, tag.name) self.assertFalse(tag.name in tag_item_names)
def assertSelectedTags(self, response, tags: List[Tag]): def assertSelectedTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode()) soup = self.make_soup(response.content.decode())
selected_tags = soup.select('p.selected-tags')[0] selected_tags = soup.select_one('p.selected-tags')
self.assertIsNotNone(selected_tags) self.assertIsNotNone(selected_tags)
tag_list = selected_tags.select('a') tag_list = selected_tags.select('a')
@@ -73,67 +86,34 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
def test_should_list_unarchived_and_user_owned_bookmarks(self): def test_should_list_unarchived_and_user_owned_bookmarks(self):
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
visible_bookmarks = [ visible_bookmarks = self.setup_numbered_bookmarks(3)
self.setup_bookmark(),
self.setup_bookmark(),
self.setup_bookmark()
]
invisible_bookmarks = [ invisible_bookmarks = [
self.setup_bookmark(is_archived=True), self.setup_bookmark(is_archived=True),
self.setup_bookmark(user=other_user), self.setup_bookmark(user=other_user),
] ]
response = self.client.get(reverse('bookmarks:index')) response = self.client.get(reverse('bookmarks:index'))
html = collapse_whitespace(response.content.decode())
# Should render list
self.assertIn('<ul class="bookmark-list" data-bookmarks-total="3">', html)
self.assertVisibleBookmarks(response, visible_bookmarks) self.assertVisibleBookmarks(response, visible_bookmarks)
self.assertInvisibleBookmarks(response, invisible_bookmarks) self.assertInvisibleBookmarks(response, invisible_bookmarks)
def test_should_list_bookmarks_matching_query(self): def test_should_list_bookmarks_matching_query(self):
visible_bookmarks = [ visible_bookmarks = self.setup_numbered_bookmarks(3, prefix='foo')
self.setup_bookmark(title='searchvalue'), invisible_bookmarks = self.setup_numbered_bookmarks(3, prefix='bar')
self.setup_bookmark(title='searchvalue'),
self.setup_bookmark(title='searchvalue')
]
invisible_bookmarks = [
self.setup_bookmark(),
self.setup_bookmark(),
self.setup_bookmark()
]
response = self.client.get(reverse('bookmarks:index') + '?q=searchvalue') response = self.client.get(reverse('bookmarks:index') + '?q=foo')
html = collapse_whitespace(response.content.decode())
# Should render list
self.assertIn('<ul class="bookmark-list" data-bookmarks-total="3">', html)
self.assertVisibleBookmarks(response, visible_bookmarks) self.assertVisibleBookmarks(response, visible_bookmarks)
self.assertInvisibleBookmarks(response, invisible_bookmarks) self.assertInvisibleBookmarks(response, invisible_bookmarks)
def test_should_list_tags_for_unarchived_and_user_owned_bookmarks(self): def test_should_list_tags_for_unarchived_and_user_owned_bookmarks(self):
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123') other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
visible_tags = [ visible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True)
self.setup_tag(), archived_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, archived=True, tag_prefix='archived')
self.setup_tag(), other_user_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, user=other_user, tag_prefix='otheruser')
self.setup_tag(),
]
invisible_tags = [
self.setup_tag(), # unused tag
self.setup_tag(), # used in archived bookmark
self.setup_tag(user=other_user), # belongs to other user
]
# Assign tags to some bookmarks with duplicates visible_tags = self.get_tags_from_bookmarks(visible_bookmarks)
self.setup_bookmark(tags=[visible_tags[0]]) invisible_tags = self.get_tags_from_bookmarks(archived_bookmarks + other_user_bookmarks)
self.setup_bookmark(tags=[visible_tags[0]])
self.setup_bookmark(tags=[visible_tags[1]])
self.setup_bookmark(tags=[visible_tags[1]])
self.setup_bookmark(tags=[visible_tags[2]])
self.setup_bookmark(tags=[visible_tags[2]])
self.setup_bookmark(tags=[invisible_tags[1]], is_archived=True)
self.setup_bookmark(tags=[invisible_tags[2]], user=other_user)
response = self.client.get(reverse('bookmarks:index')) response = self.client.get(reverse('bookmarks:index'))
@@ -141,29 +121,38 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
self.assertInvisibleTags(response, invisible_tags) self.assertInvisibleTags(response, invisible_tags)
def test_should_list_tags_for_bookmarks_matching_query(self): def test_should_list_tags_for_bookmarks_matching_query(self):
visible_tags = [ visible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, prefix='foo', tag_prefix='foo')
self.setup_tag(), invisible_bookmarks = self.setup_numbered_bookmarks(3, with_tags=True, prefix='bar', tag_prefix='bar')
self.setup_tag(),
self.setup_tag(),
]
invisible_tags = [
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
]
self.setup_bookmark(tags=[visible_tags[0]], title='searchvalue') visible_tags = self.get_tags_from_bookmarks(visible_bookmarks)
self.setup_bookmark(tags=[visible_tags[1]], title='searchvalue') invisible_tags = self.get_tags_from_bookmarks(invisible_bookmarks)
self.setup_bookmark(tags=[visible_tags[2]], title='searchvalue')
self.setup_bookmark(tags=[invisible_tags[0]])
self.setup_bookmark(tags=[invisible_tags[1]])
self.setup_bookmark(tags=[invisible_tags[2]])
response = self.client.get(reverse('bookmarks:index') + '?q=searchvalue') response = self.client.get(reverse('bookmarks:index') + '?q=foo')
self.assertVisibleTags(response, visible_tags) self.assertVisibleTags(response, visible_tags)
self.assertInvisibleTags(response, invisible_tags) self.assertInvisibleTags(response, invisible_tags)
def test_should_list_bookmarks_and_tags_for_search_preferences(self):
user_profile = self.user.profile
user_profile.search_preferences = {
'unread': BookmarkSearch.FILTER_UNREAD_YES,
}
user_profile.save()
unread_bookmarks = self.setup_numbered_bookmarks(3, unread=True, with_tags=True, prefix='unread',
tag_prefix='unread')
read_bookmarks = self.setup_numbered_bookmarks(3, unread=False, with_tags=True, prefix='read',
tag_prefix='read')
unread_tags = self.get_tags_from_bookmarks(unread_bookmarks)
read_tags = self.get_tags_from_bookmarks(read_bookmarks)
response = self.client.get(reverse('bookmarks:index'))
self.assertVisibleBookmarks(response, unread_bookmarks)
self.assertInvisibleBookmarks(response, read_bookmarks)
self.assertVisibleTags(response, unread_tags)
self.assertInvisibleTags(response, read_tags)
def test_should_display_selected_tags_from_query(self): def test_should_display_selected_tags_from_query(self):
tags = [ tags = [
self.setup_tag(), self.setup_tag(),
@@ -210,11 +199,7 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
self.assertSelectedTags(response, [tags[0], tags[1]]) self.assertSelectedTags(response, [tags[0], tags[1]])
def test_should_open_bookmarks_in_new_page_by_default(self): def test_should_open_bookmarks_in_new_page_by_default(self):
visible_bookmarks = [ visible_bookmarks = self.setup_numbered_bookmarks(3)
self.setup_bookmark(),
self.setup_bookmark(),
self.setup_bookmark()
]
response = self.client.get(reverse('bookmarks:index')) response = self.client.get(reverse('bookmarks:index'))
@@ -225,11 +210,7 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
user.profile.bookmark_link_target = UserProfile.BOOKMARK_LINK_TARGET_SELF user.profile.bookmark_link_target = UserProfile.BOOKMARK_LINK_TARGET_SELF
user.profile.save() user.profile.save()
visible_bookmarks = [ visible_bookmarks = self.setup_numbered_bookmarks(3)
self.setup_bookmark(),
self.setup_bookmark(),
self.setup_bookmark()
]
response = self.client.get(reverse('bookmarks:index')) response = self.client.get(reverse('bookmarks:index'))
@@ -328,6 +309,106 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
</select> </select>
''', html) ''', html)
def test_apply_search_preferences(self):
# no params
response = self.client.post(reverse('bookmarks:index'))
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse('bookmarks:index'))
# some params
response = self.client.post(reverse('bookmarks:index'), {
'q': 'foo',
'sort': BookmarkSearch.SORT_TITLE_ASC,
})
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse('bookmarks:index') + '?q=foo&sort=title_asc')
# params with default value are removed
response = self.client.post(reverse('bookmarks:index'), {
'q': 'foo',
'user': '',
'sort': BookmarkSearch.SORT_ADDED_DESC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse('bookmarks:index') + '?q=foo&unread=yes')
# page is removed
response = self.client.post(reverse('bookmarks:index'), {
'q': 'foo',
'page': '2',
'sort': BookmarkSearch.SORT_TITLE_ASC,
})
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse('bookmarks:index') + '?q=foo&sort=title_asc')
def test_save_search_preferences(self):
user_profile = self.user.profile
# no params
self.client.post(reverse('bookmarks:index'), {
'save': '',
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_ADDED_DESC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
})
# with param
self.client.post(reverse('bookmarks:index'), {
'save': '',
'sort': BookmarkSearch.SORT_TITLE_ASC,
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
})
# add a param
self.client.post(reverse('bookmarks:index'), {
'save': '',
'sort': BookmarkSearch.SORT_TITLE_ASC,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
# remove a param
self.client.post(reverse('bookmarks:index'), {
'save': '',
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_ADDED_DESC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
# ignores non-preferences
self.client.post(reverse('bookmarks:index'), {
'save': '',
'q': 'foo',
'user': 'john',
'page': '3',
'sort': BookmarkSearch.SORT_TITLE_ASC,
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
})
def test_url_encode_bookmark_actions_url(self): def test_url_encode_bookmark_actions_url(self):
url = reverse('bookmarks:index') + '?q=%23foo' url = reverse('bookmarks:index') + '?q=%23foo'
response = self.client.get(url) response = self.client.get(url)

View File

@@ -10,10 +10,10 @@ class BookmarkSearchFormTest(TestCase, BookmarkFactoryMixin):
search = BookmarkSearch() search = BookmarkSearch()
form = BookmarkSearchForm(search) form = BookmarkSearchForm(search)
self.assertEqual(form['q'].initial, '') self.assertEqual(form['q'].initial, '')
self.assertEqual(form['sort'].initial, BookmarkSearch.SORT_ADDED_DESC)
self.assertEqual(form['user'].initial, '') self.assertEqual(form['user'].initial, '')
self.assertEqual(form['shared'].initial, '') self.assertEqual(form['sort'].initial, BookmarkSearch.SORT_ADDED_DESC)
self.assertEqual(form['unread'].initial, '') self.assertEqual(form['shared'].initial, BookmarkSearch.FILTER_SHARED_OFF)
self.assertEqual(form['unread'].initial, BookmarkSearch.FILTER_UNREAD_OFF)
# with params # with params
search = BookmarkSearch(q='search query', search = BookmarkSearch(q='search query',
@@ -23,8 +23,8 @@ class BookmarkSearchFormTest(TestCase, BookmarkFactoryMixin):
unread=BookmarkSearch.FILTER_UNREAD_YES) unread=BookmarkSearch.FILTER_UNREAD_YES)
form = BookmarkSearchForm(search) form = BookmarkSearchForm(search)
self.assertEqual(form['q'].initial, 'search query') 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['user'].initial, 'user123')
self.assertEqual(form['sort'].initial, BookmarkSearch.SORT_ADDED_ASC)
self.assertEqual(form['shared'].initial, BookmarkSearch.FILTER_SHARED_SHARED) self.assertEqual(form['shared'].initial, BookmarkSearch.FILTER_SHARED_SHARED)
self.assertEqual(form['unread'].initial, BookmarkSearch.FILTER_UNREAD_YES) self.assertEqual(form['unread'].initial, BookmarkSearch.FILTER_UNREAD_YES)

View File

@@ -1,50 +1,70 @@
from unittest.mock import Mock from django.http import QueryDict
from bookmarks.models import BookmarkSearch
from django.test import TestCase from django.test import TestCase
from bookmarks.models import BookmarkSearch
class BookmarkSearchModelTest(TestCase): class BookmarkSearchModelTest(TestCase):
def test_from_request(self): def test_from_request(self):
# no params # no params
mock_request = Mock() query_dict = QueryDict()
mock_request.GET = {}
search = BookmarkSearch.from_request(mock_request) search = BookmarkSearch.from_request(query_dict)
self.assertEqual(search.q, '') self.assertEqual(search.q, '')
self.assertEqual(search.sort, BookmarkSearch.SORT_ADDED_DESC)
self.assertEqual(search.user, '') self.assertEqual(search.user, '')
self.assertEqual(search.shared, '') self.assertEqual(search.sort, BookmarkSearch.SORT_ADDED_DESC)
self.assertEqual(search.unread, '') self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_OFF)
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF)
# some params # some params
mock_request.GET = { query_dict = QueryDict('q=search query&user=user123')
'q': 'search query',
'user': 'user123',
}
bookmark_search = BookmarkSearch.from_request(mock_request) bookmark_search = BookmarkSearch.from_request(query_dict)
self.assertEqual(bookmark_search.q, 'search query') self.assertEqual(bookmark_search.q, 'search query')
self.assertEqual(bookmark_search.sort, BookmarkSearch.SORT_ADDED_DESC)
self.assertEqual(bookmark_search.user, 'user123') self.assertEqual(bookmark_search.user, 'user123')
self.assertEqual(bookmark_search.shared, '') self.assertEqual(bookmark_search.sort, BookmarkSearch.SORT_ADDED_DESC)
self.assertEqual(bookmark_search.unread, '') self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_OFF)
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF)
# all params # all params
mock_request.GET = { query_dict = QueryDict('q=search query&sort=title_asc&user=user123&shared=yes&unread=yes')
'q': 'search query',
'user': 'user123',
'sort': BookmarkSearch.SORT_TITLE_ASC,
'shared': BookmarkSearch.FILTER_SHARED_SHARED,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
}
search = BookmarkSearch.from_request(mock_request) search = BookmarkSearch.from_request(query_dict)
self.assertEqual(search.q, 'search query') self.assertEqual(search.q, 'search query')
self.assertEqual(search.user, 'user123') self.assertEqual(search.user, 'user123')
self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_ASC) self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_ASC)
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_SHARED) self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_SHARED)
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_YES) self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_YES)
# respects preferences
preferences = {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
}
query_dict = QueryDict('q=search query')
search = BookmarkSearch.from_request(query_dict, preferences)
self.assertEqual(search.q, 'search query')
self.assertEqual(search.user, '')
self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_ASC)
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_OFF)
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_YES)
# query overrides preferences
preferences = {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'shared': BookmarkSearch.FILTER_SHARED_SHARED,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
}
query_dict = QueryDict('sort=title_desc&shared=no&unread=off')
search = BookmarkSearch.from_request(query_dict, preferences)
self.assertEqual(search.q, '')
self.assertEqual(search.user, '')
self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_DESC)
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_UNSHARED)
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF)
def test_modified_params(self): def test_modified_params(self):
# no params # no params
bookmark_search = BookmarkSearch() bookmark_search = BookmarkSearch()
@@ -69,3 +89,74 @@ class BookmarkSearchModelTest(TestCase):
unread=BookmarkSearch.FILTER_UNREAD_YES) unread=BookmarkSearch.FILTER_UNREAD_YES)
modified_params = bookmark_search.modified_params modified_params = bookmark_search.modified_params
self.assertCountEqual(modified_params, ['q', 'sort', 'user', 'shared', 'unread']) self.assertCountEqual(modified_params, ['q', 'sort', 'user', 'shared', 'unread'])
# preferences are not modified params
preferences = {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
}
bookmark_search = BookmarkSearch(preferences=preferences)
modified_params = bookmark_search.modified_params
self.assertEqual(len(modified_params), 0)
# param is not modified if it matches the preference
preferences = {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
}
bookmark_search = BookmarkSearch(sort=BookmarkSearch.SORT_TITLE_ASC,
unread=BookmarkSearch.FILTER_UNREAD_YES,
preferences=preferences)
modified_params = bookmark_search.modified_params
self.assertEqual(len(modified_params), 0)
# overriding preferences is a modified param
preferences = {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'shared': BookmarkSearch.FILTER_SHARED_SHARED,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
}
bookmark_search = BookmarkSearch(sort=BookmarkSearch.SORT_TITLE_DESC,
shared=BookmarkSearch.FILTER_SHARED_UNSHARED,
unread=BookmarkSearch.FILTER_UNREAD_OFF,
preferences=preferences)
modified_params = bookmark_search.modified_params
self.assertCountEqual(modified_params, ['sort', 'shared', 'unread'])
def test_has_modifications(self):
# no params
bookmark_search = BookmarkSearch()
self.assertFalse(bookmark_search.has_modifications)
# params are default values
bookmark_search = BookmarkSearch(q='', sort=BookmarkSearch.SORT_ADDED_DESC, user='', shared='')
self.assertFalse(bookmark_search.has_modifications)
# modified params
bookmark_search = BookmarkSearch(q='search query', sort=BookmarkSearch.SORT_ADDED_ASC)
self.assertTrue(bookmark_search.has_modifications)
def test_preferences_dict(self):
# no params
bookmark_search = BookmarkSearch()
self.assertEqual(bookmark_search.preferences_dict, {
'sort': BookmarkSearch.SORT_ADDED_DESC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
})
# with params
bookmark_search = BookmarkSearch(sort=BookmarkSearch.SORT_TITLE_DESC, unread=BookmarkSearch.FILTER_UNREAD_YES)
self.assertEqual(bookmark_search.preferences_dict, {
'sort': BookmarkSearch.SORT_TITLE_DESC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
# only returns preferences
bookmark_search = BookmarkSearch(q='search query', user='user123')
self.assertEqual(bookmark_search.preferences_dict, {
'sort': BookmarkSearch.SORT_ADDED_DESC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
})

View File

@@ -1,58 +1,226 @@
from bs4 import BeautifulSoup
from django.db.models import QuerySet from django.db.models import QuerySet
from django.template import Template, RequestContext from django.template import Template, RequestContext
from django.test import TestCase, RequestFactory from django.test import TestCase, RequestFactory
from bookmarks.models import BookmarkSearch, Tag from bookmarks.models import BookmarkSearch, Tag
from bookmarks.tests.helpers import BookmarkFactoryMixin from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
class BookmarkSearchTagTest(TestCase, BookmarkFactoryMixin): class BookmarkSearchTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
def render_template(self, url: str, tags: QuerySet[Tag] = Tag.objects.all()): def render_template(self, url: str, tags: QuerySet[Tag] = Tag.objects.all(), mode: str = ''):
rf = RequestFactory() rf = RequestFactory()
request = rf.get(url) request = rf.get(url)
request.user = self.get_or_create_test_user() request.user = self.get_or_create_test_user()
request.user_profile = self.get_or_create_test_user().profile request.user_profile = self.get_or_create_test_user().profile
search = BookmarkSearch.from_request(request) search = BookmarkSearch.from_request(request.GET)
context = RequestContext(request, { context = RequestContext(request, {
'request': request, 'request': request,
'search': search, 'search': search,
'tags': tags, 'tags': tags,
'mode': mode,
}) })
template_to_render = Template( template_to_render = Template(
'{% load bookmarks %}' '{% load bookmarks %}'
'{% bookmark_search search tags %}' '{% bookmark_search search tags mode %}'
) )
return template_to_render.render(context) return template_to_render.render(context)
def assertHiddenInput(self, html: str, name: str, value: str = None): def assertHiddenInput(self, form: BeautifulSoup, name: str, value: str = None):
needle = f'<input type="hidden" name="{name}"' input = form.select_one(f'input[name="{name}"][type="hidden"]')
self.assertIsNotNone(input)
if value is not None: if value is not None:
needle += f' value="{value}"' self.assertEqual(input['value'], value)
self.assertIn(needle, html) def assertNoHiddenInput(self, form: BeautifulSoup, name: str):
input = form.select_one(f'input[name="{name}"][type="hidden"]')
self.assertIsNone(input)
def assertNoHiddenInput(self, html: str, name: str): def assertSearchInput(self, form: BeautifulSoup, name: str, value: str = None):
needle = f'<input type="hidden" name="{name}"' input = form.select_one(f'input[name="{name}"][type="search"]')
self.assertIsNotNone(input)
self.assertNotIn(needle, html) if value is not None:
self.assertEqual(input['value'], value)
def test_hidden_inputs(self): def assertSelect(self, form: BeautifulSoup, name: str, value: str = None):
select = form.select_one(f'select[name="{name}"]')
self.assertIsNotNone(select)
if value is not None:
options = select.select('option')
for option in options:
if option['value'] == value:
self.assertTrue(option.has_attr('selected'))
else:
self.assertFalse(option.has_attr('selected'))
def assertRadioGroup(self, form: BeautifulSoup, name: str, value: str = None):
radios = form.select(f'input[name="{name}"][type="radio"]')
self.assertTrue(len(radios) > 0)
if value is not None:
for radio in radios:
if radio['value'] == value:
self.assertTrue(radio.has_attr('checked'))
else:
self.assertFalse(radio.has_attr('checked'))
def assertNoRadioGroup(self, form: BeautifulSoup, name: str):
radios = form.select(f'input[name="{name}"][type="radio"]')
self.assertTrue(len(radios) == 0)
def assertUnmodifiedLabel(self, html: str, text: str, id: str = ''):
id_attr = f'for="{id}"' if id else ''
tag = 'label' if id else 'div'
needle = f'<{tag} class="form-label" {id_attr}>{text}</{tag}>'
self.assertInHTML(needle, html)
def assertModifiedLabel(self, html: str, text: str, id: str = ''):
id_attr = f'for="{id}"' if id else ''
tag = 'label' if id else 'div'
needle = f'<{tag} class="form-label text-bold" {id_attr}>{text}</{tag}>'
self.assertInHTML(needle, html)
def test_search_form_inputs(self):
# Without params # Without params
url = '/test' url = '/test'
rendered_template = self.render_template(url) rendered_template = self.render_template(url)
soup = self.make_soup(rendered_template)
search_form = soup.select_one('form#search')
self.assertNoHiddenInput(rendered_template, 'user') self.assertSearchInput(search_form, 'q')
self.assertNoHiddenInput(rendered_template, 'q') self.assertNoHiddenInput(search_form, 'user')
self.assertNoHiddenInput(rendered_template, 'sort') self.assertNoHiddenInput(search_form, 'sort')
self.assertNoHiddenInput(rendered_template, 'shared') self.assertNoHiddenInput(search_form, 'shared')
self.assertNoHiddenInput(rendered_template, 'unread') self.assertNoHiddenInput(search_form, 'unread')
# With params # With params
url = '/test?q=foo&user=john&sort=title_asc&shared=shared&unread=yes' url = '/test?q=foo&user=john&sort=title_asc&shared=yes&unread=yes'
rendered_template = self.render_template(url)
soup = self.make_soup(rendered_template)
search_form = soup.select_one('form#search')
self.assertSearchInput(search_form, 'q', 'foo')
self.assertHiddenInput(search_form, 'user', 'john')
self.assertHiddenInput(search_form, 'sort', BookmarkSearch.SORT_TITLE_ASC)
self.assertHiddenInput(search_form, 'shared', BookmarkSearch.FILTER_SHARED_SHARED)
self.assertHiddenInput(search_form, 'unread', BookmarkSearch.FILTER_UNREAD_YES)
def test_preferences_form_inputs(self):
# Without params
url = '/test'
rendered_template = self.render_template(url)
soup = self.make_soup(rendered_template)
preferences_form = soup.select_one('form#search_preferences')
self.assertNoHiddenInput(preferences_form, 'q')
self.assertNoHiddenInput(preferences_form, 'user')
self.assertNoHiddenInput(preferences_form, 'sort')
self.assertNoHiddenInput(preferences_form, 'shared')
self.assertNoHiddenInput(preferences_form, 'unread')
self.assertSelect(preferences_form, 'sort', BookmarkSearch.SORT_ADDED_DESC)
self.assertRadioGroup(preferences_form, 'shared', BookmarkSearch.FILTER_SHARED_OFF)
self.assertRadioGroup(preferences_form, 'unread', BookmarkSearch.FILTER_UNREAD_OFF)
# With params
url = '/test?q=foo&user=john&sort=title_asc&shared=yes&unread=yes'
rendered_template = self.render_template(url)
soup = self.make_soup(rendered_template)
preferences_form = soup.select_one('form#search_preferences')
self.assertHiddenInput(preferences_form, 'q', 'foo')
self.assertHiddenInput(preferences_form, 'user', 'john')
self.assertNoHiddenInput(preferences_form, 'sort')
self.assertNoHiddenInput(preferences_form, 'shared')
self.assertNoHiddenInput(preferences_form, 'unread')
self.assertSelect(preferences_form, 'sort', BookmarkSearch.SORT_TITLE_ASC)
self.assertRadioGroup(preferences_form, 'shared', BookmarkSearch.FILTER_SHARED_SHARED)
self.assertRadioGroup(preferences_form, 'unread', BookmarkSearch.FILTER_UNREAD_YES)
def test_preferences_form_inputs_shared_mode(self):
# Without params
url = '/test'
rendered_template = self.render_template(url, mode='shared')
soup = self.make_soup(rendered_template)
preferences_form = soup.select_one('form#search_preferences')
self.assertNoHiddenInput(preferences_form, 'q')
self.assertNoHiddenInput(preferences_form, 'user')
self.assertNoHiddenInput(preferences_form, 'sort')
self.assertNoHiddenInput(preferences_form, 'shared')
self.assertNoHiddenInput(preferences_form, 'unread')
self.assertSelect(preferences_form, 'sort', BookmarkSearch.SORT_ADDED_DESC)
self.assertNoRadioGroup(preferences_form, 'shared')
self.assertNoRadioGroup(preferences_form, 'unread')
# With params
url = '/test?q=foo&user=john&sort=title_asc'
rendered_template = self.render_template(url, mode='shared')
soup = self.make_soup(rendered_template)
preferences_form = soup.select_one('form#search_preferences')
self.assertHiddenInput(preferences_form, 'q', 'foo')
self.assertHiddenInput(preferences_form, 'user', 'john')
self.assertNoHiddenInput(preferences_form, 'sort')
self.assertNoHiddenInput(preferences_form, 'shared')
self.assertNoHiddenInput(preferences_form, 'unread')
self.assertSelect(preferences_form, 'sort', BookmarkSearch.SORT_TITLE_ASC)
self.assertNoRadioGroup(preferences_form, 'shared')
self.assertNoRadioGroup(preferences_form, 'unread')
def test_modified_indicator(self):
# Without modifications
url = '/test'
rendered_template = self.render_template(url) rendered_template = self.render_template(url)
self.assertHiddenInput(rendered_template, 'user', 'john') self.assertIn('<button type="button" class="btn dropdown-toggle">', rendered_template)
self.assertNoHiddenInput(rendered_template, 'q')
self.assertNoHiddenInput(rendered_template, 'sort') # With modifications
self.assertNoHiddenInput(rendered_template, 'shared') url = '/test?sort=title_asc'
self.assertNoHiddenInput(rendered_template, 'unread') rendered_template = self.render_template(url)
self.assertIn('<button type="button" class="btn dropdown-toggle badge">', rendered_template)
# Ignores non-preferences modifications
url = '/test?q=foo&user=john'
rendered_template = self.render_template(url)
self.assertIn('<button type="button" class="btn dropdown-toggle">', rendered_template)
def test_modified_labels(self):
# Without modifications
url = '/test'
rendered_template = self.render_template(url)
self.assertUnmodifiedLabel(rendered_template, 'Sort by', 'id_sort')
self.assertUnmodifiedLabel(rendered_template, 'Shared filter')
self.assertUnmodifiedLabel(rendered_template, 'Unread filter')
# Modified sort
url = '/test?sort=title_asc'
rendered_template = self.render_template(url)
self.assertModifiedLabel(rendered_template, 'Sort by', 'id_sort')
self.assertUnmodifiedLabel(rendered_template, 'Shared filter')
self.assertUnmodifiedLabel(rendered_template, 'Unread filter')
# Modified shared
url = '/test?shared=yes'
rendered_template = self.render_template(url)
self.assertUnmodifiedLabel(rendered_template, 'Sort by', 'id_sort')
self.assertModifiedLabel(rendered_template, 'Shared filter')
self.assertUnmodifiedLabel(rendered_template, 'Unread filter')
# Modified unread
url = '/test?unread=yes'
rendered_template = self.render_template(url)
self.assertUnmodifiedLabel(rendered_template, 'Sort by', 'id_sort')
self.assertUnmodifiedLabel(rendered_template, 'Shared filter')
self.assertModifiedLabel(rendered_template, 'Unread filter')

View File

@@ -5,8 +5,8 @@ from django.contrib.auth.models import User
from django.test import TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from bookmarks.models import Bookmark, Tag, UserProfile from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile
from bookmarks.tests.helpers import BookmarkFactoryMixin, collapse_whitespace, HtmlTestMixin from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin): class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
@@ -22,27 +22,47 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
) )
def assertVisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'): def assertVisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'):
html = response.content.decode() soup = self.make_soup(response.content.decode())
self.assertContains(response, '<li ld-bookmark-item class="shared">', count=len(bookmarks)) bookmark_list = soup.select_one(f'ul.bookmark-list[data-bookmarks-total="{len(bookmarks)}"]')
self.assertIsNotNone(bookmark_list)
bookmark_items = bookmark_list.select('li[ld-bookmark-item]')
self.assertEqual(len(bookmark_items), len(bookmarks))
for bookmark in bookmarks: for bookmark in bookmarks:
self.assertBookmarkCount(html, bookmark, 1, link_target) bookmark_item = bookmark_list.select_one(
f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]')
self.assertIsNotNone(bookmark_item)
def assertInvisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'): def assertInvisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'):
html = response.content.decode() soup = self.make_soup(response.content.decode())
for bookmark in bookmarks: for bookmark in bookmarks:
self.assertBookmarkCount(html, bookmark, 0, link_target) bookmark_item = soup.select_one(
f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]')
self.assertIsNone(bookmark_item)
def assertVisibleTags(self, response, tags: [Tag]): def assertVisibleTags(self, response, tags: List[Tag]):
self.assertContains(response, 'data-is-tag-item', count=len(tags)) soup = self.make_soup(response.content.decode())
tag_cloud = soup.select_one('div.tag-cloud')
self.assertIsNotNone(tag_cloud)
tag_items = tag_cloud.select('a[data-is-tag-item]')
self.assertEqual(len(tag_items), len(tags))
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
for tag in tags: for tag in tags:
self.assertContains(response, tag.name) self.assertTrue(tag.name in tag_item_names)
def assertInvisibleTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
tag_items = soup.select('a[data-is-tag-item]')
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
def assertInvisibleTags(self, response, tags: [Tag]):
for tag in tags: for tag in tags:
self.assertNotContains(response, tag.name) self.assertFalse(tag.name in tag_item_names)
def assertVisibleUserOptions(self, response, users: List[User]): def assertVisibleUserOptions(self, response, users: List[User]):
html = response.content.decode() html = response.content.decode()
@@ -86,10 +106,7 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
] ]
response = self.client.get(reverse('bookmarks:shared')) response = self.client.get(reverse('bookmarks:shared'))
html = collapse_whitespace(response.content.decode())
# Should render list
self.assertIn('<ul class="bookmark-list" data-bookmarks-total="3">', html)
self.assertVisibleBookmarks(response, visible_bookmarks) self.assertVisibleBookmarks(response, visible_bookmarks)
self.assertInvisibleBookmarks(response, invisible_bookmarks) self.assertInvisibleBookmarks(response, invisible_bookmarks)
@@ -116,22 +133,12 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
def test_should_list_bookmarks_matching_query(self): def test_should_list_bookmarks_matching_query(self):
self.authenticate() self.authenticate()
user = self.setup_user(enable_sharing=True) user = self.setup_user(enable_sharing=True)
visible_bookmarks = [
self.setup_bookmark(shared=True, title='searchvalue', user=user),
self.setup_bookmark(shared=True, title='searchvalue', user=user),
self.setup_bookmark(shared=True, title='searchvalue', user=user)
]
invisible_bookmarks = [
self.setup_bookmark(shared=True, user=user),
self.setup_bookmark(shared=True, user=user),
self.setup_bookmark(shared=True, user=user)
]
response = self.client.get(reverse('bookmarks:shared') + '?q=searchvalue') visible_bookmarks = self.setup_numbered_bookmarks(3, shared=True, user=user, prefix='foo')
html = collapse_whitespace(response.content.decode()) invisible_bookmarks = self.setup_numbered_bookmarks(3, shared=True, user=user)
response = self.client.get(reverse('bookmarks:shared') + '?q=foo')
# Should render list
self.assertIn('<ul class="bookmark-list" data-bookmarks-total="3">', html)
self.assertVisibleBookmarks(response, visible_bookmarks) self.assertVisibleBookmarks(response, visible_bookmarks)
self.assertInvisibleBookmarks(response, invisible_bookmarks) self.assertInvisibleBookmarks(response, invisible_bookmarks)
@@ -139,22 +146,11 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
user1 = self.setup_user(enable_sharing=True, enable_public_sharing=True) user1 = self.setup_user(enable_sharing=True, enable_public_sharing=True)
user2 = self.setup_user(enable_sharing=True) user2 = self.setup_user(enable_sharing=True)
visible_bookmarks = [ visible_bookmarks = self.setup_numbered_bookmarks(3, shared=True, user=user1, prefix='user1')
self.setup_bookmark(shared=True, user=user1), invisible_bookmarks = self.setup_numbered_bookmarks(3, shared=True, user=user2, prefix='user2')
self.setup_bookmark(shared=True, user=user1),
self.setup_bookmark(shared=True, user=user1),
]
invisible_bookmarks = [
self.setup_bookmark(shared=True, user=user2),
self.setup_bookmark(shared=True, user=user2),
self.setup_bookmark(shared=True, user=user2),
]
response = self.client.get(reverse('bookmarks:shared')) response = self.client.get(reverse('bookmarks:shared'))
html = collapse_whitespace(response.content.decode())
# Should render list
self.assertIn('<ul class="bookmark-list" data-bookmarks-total="3">', html)
self.assertVisibleBookmarks(response, visible_bookmarks) self.assertVisibleBookmarks(response, visible_bookmarks)
self.assertInvisibleBookmarks(response, invisible_bookmarks) self.assertInvisibleBookmarks(response, invisible_bookmarks)
@@ -297,6 +293,30 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
response = self.client.get(reverse('bookmarks:shared')) response = self.client.get(reverse('bookmarks:shared'))
self.assertVisibleUserOptions(response, expected_visible_users) self.assertVisibleUserOptions(response, expected_visible_users)
def test_should_list_bookmarks_and_tags_for_search_preferences(self):
self.authenticate()
other_user = self.setup_user(enable_sharing=True)
user_profile = self.get_or_create_test_user().profile
user_profile.search_preferences = {
'unread': BookmarkSearch.FILTER_UNREAD_YES,
}
user_profile.save()
unread_bookmarks = self.setup_numbered_bookmarks(3, shared=True, unread=True, with_tags=True, prefix='unread',
tag_prefix='unread', user=other_user)
read_bookmarks = self.setup_numbered_bookmarks(3, shared=True, unread=False, with_tags=True, prefix='read',
tag_prefix='read', user=other_user)
unread_tags = self.get_tags_from_bookmarks(unread_bookmarks)
read_tags = self.get_tags_from_bookmarks(read_bookmarks)
response = self.client.get(reverse('bookmarks:shared'))
self.assertVisibleBookmarks(response, unread_bookmarks)
self.assertInvisibleBookmarks(response, read_bookmarks)
self.assertVisibleTags(response, unread_tags)
self.assertInvisibleTags(response, read_tags)
def test_should_open_bookmarks_in_new_page_by_default(self): def test_should_open_bookmarks_in_new_page_by_default(self):
self.authenticate() self.authenticate()
user = self.get_or_create_test_user() user = self.get_or_create_test_user()
@@ -370,6 +390,107 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
response = self.client.get(base_url + url_params) response = self.client.get(base_url + url_params)
self.assertEditLink(response, url) self.assertEditLink(response, url)
def test_apply_search_preferences(self):
# no params
response = self.client.post(reverse('bookmarks:shared'))
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse('bookmarks:shared'))
# some params
response = self.client.post(reverse('bookmarks:shared'), {
'q': 'foo',
'sort': BookmarkSearch.SORT_TITLE_ASC,
})
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse('bookmarks:shared') + '?q=foo&sort=title_asc')
# params with default value are removed
response = self.client.post(reverse('bookmarks:shared'), {
'q': 'foo',
'user': '',
'sort': BookmarkSearch.SORT_ADDED_DESC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse('bookmarks:shared') + '?q=foo&unread=yes')
# page is removed
response = self.client.post(reverse('bookmarks:shared'), {
'q': 'foo',
'page': '2',
'sort': BookmarkSearch.SORT_TITLE_ASC,
})
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse('bookmarks:shared') + '?q=foo&sort=title_asc')
def test_save_search_preferences(self):
self.authenticate()
user_profile = self.user.profile
# no params
self.client.post(reverse('bookmarks:shared'), {
'save': '',
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_ADDED_DESC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
})
# with param
self.client.post(reverse('bookmarks:shared'), {
'save': '',
'sort': BookmarkSearch.SORT_TITLE_ASC,
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
})
# add a param
self.client.post(reverse('bookmarks:shared'), {
'save': '',
'sort': BookmarkSearch.SORT_TITLE_ASC,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
# remove a param
self.client.post(reverse('bookmarks:shared'), {
'save': '',
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_ADDED_DESC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_YES,
})
# ignores non-preferences
self.client.post(reverse('bookmarks:shared'), {
'save': '',
'q': 'foo',
'user': 'john',
'page': '3',
'sort': BookmarkSearch.SORT_TITLE_ASC,
})
user_profile.refresh_from_db()
self.assertEqual(user_profile.search_preferences, {
'sort': BookmarkSearch.SORT_TITLE_ASC,
'shared': BookmarkSearch.FILTER_SHARED_OFF,
'unread': BookmarkSearch.FILTER_UNREAD_OFF,
})
def test_url_encode_bookmark_actions_url(self): def test_url_encode_bookmark_actions_url(self):
url = reverse('bookmarks:shared') + '?q=%23foo' url = reverse('bookmarks:shared') + '?q=%23foo'
response = self.client.get(url) response = self.client.get(url)

View File

@@ -99,12 +99,12 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
self.assertBookmarkListEqual(response.data['results'], unshared_bookmarks + shared_bookmarks) self.assertBookmarkListEqual(response.data['results'], unshared_bookmarks + shared_bookmarks)
# Filter shared # Filter shared
response = self.get(reverse('bookmarks:bookmark-list') + '?shared=shared', response = self.get(reverse('bookmarks:bookmark-list') + '?shared=yes',
expected_status_code=status.HTTP_200_OK) expected_status_code=status.HTTP_200_OK)
self.assertBookmarkListEqual(response.data['results'], shared_bookmarks) self.assertBookmarkListEqual(response.data['results'], shared_bookmarks)
# Filter unshared # Filter unshared
response = self.get(reverse('bookmarks:bookmark-list') + '?shared=unshared', response = self.get(reverse('bookmarks:bookmark-list') + '?shared=no',
expected_status_code=status.HTTP_200_OK) expected_status_code=status.HTTP_200_OK)
self.assertBookmarkListEqual(response.data['results'], unshared_bookmarks) self.assertBookmarkListEqual(response.data['results'], unshared_bookmarks)

View File

@@ -146,12 +146,6 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(tags=[tag1, tag2, self.setup_tag()]), self.setup_bookmark(tags=[tag1, tag2, self.setup_tag()]),
] ]
def get_tags_from_bookmarks(self, bookmarks: [Bookmark]):
all_tags = []
for bookmark in bookmarks:
all_tags = all_tags + list(bookmark.tags.all())
return all_tags
def assertQueryResult(self, query: QuerySet, item_lists: [[any]]): def assertQueryResult(self, query: QuerySet, item_lists: [[any]]):
expected_items = [] expected_items = []
for item_list in item_lists: for item_list in item_lists:
@@ -164,7 +158,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def test_query_bookmarks_should_return_all_for_empty_query(self): def test_query_bookmarks_should_return_all_for_empty_query(self):
self.setup_bookmark_search_data() self.setup_bookmark_search_data()
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q=''))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.other_bookmarks, self.other_bookmarks,
self.term1_bookmarks, self.term1_bookmarks,
@@ -179,7 +173,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def test_query_bookmarks_should_search_single_term(self): def test_query_bookmarks_should_search_single_term(self):
self.setup_bookmark_search_data() self.setup_bookmark_search_data()
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='term1')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='term1'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.term1_bookmarks, self.term1_bookmarks,
self.term1_term2_bookmarks, self.term1_term2_bookmarks,
@@ -189,35 +183,35 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def test_query_bookmarks_should_search_multiple_terms(self): def test_query_bookmarks_should_search_multiple_terms(self):
self.setup_bookmark_search_data() self.setup_bookmark_search_data()
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='term2 term1')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='term2 term1'))
self.assertQueryResult(query, [self.term1_term2_bookmarks]) self.assertQueryResult(query, [self.term1_term2_bookmarks])
def test_query_bookmarks_should_search_single_tag(self): def test_query_bookmarks_should_search_single_tag(self):
self.setup_bookmark_search_data() self.setup_bookmark_search_data()
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='#tag1')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='#tag1'))
self.assertQueryResult(query, [self.tag1_bookmarks, self.tag1_tag2_bookmarks, self.term1_tag1_bookmarks]) self.assertQueryResult(query, [self.tag1_bookmarks, self.tag1_tag2_bookmarks, self.term1_tag1_bookmarks])
def test_query_bookmarks_should_search_multiple_tags(self): def test_query_bookmarks_should_search_multiple_tags(self):
self.setup_bookmark_search_data() self.setup_bookmark_search_data()
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='#tag1 #tag2')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='#tag1 #tag2'))
self.assertQueryResult(query, [self.tag1_tag2_bookmarks]) self.assertQueryResult(query, [self.tag1_tag2_bookmarks])
def test_query_bookmarks_should_search_multiple_tags_ignoring_casing(self): def test_query_bookmarks_should_search_multiple_tags_ignoring_casing(self):
self.setup_bookmark_search_data() self.setup_bookmark_search_data()
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='#Tag1 #TAG2')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='#Tag1 #TAG2'))
self.assertQueryResult(query, [self.tag1_tag2_bookmarks]) self.assertQueryResult(query, [self.tag1_tag2_bookmarks])
def test_query_bookmarks_should_search_terms_and_tags_combined(self): def test_query_bookmarks_should_search_terms_and_tags_combined(self):
self.setup_bookmark_search_data() self.setup_bookmark_search_data()
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='term1 #tag1')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='term1 #tag1'))
self.assertQueryResult(query, [self.term1_tag1_bookmarks]) self.assertQueryResult(query, [self.term1_tag1_bookmarks])
@@ -227,7 +221,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.profile.tag_search = UserProfile.TAG_SEARCH_STRICT self.profile.tag_search = UserProfile.TAG_SEARCH_STRICT
self.profile.save() self.profile.save()
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='tag1')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='tag1'))
self.assertQueryResult(query, [self.tag1_as_term_bookmarks]) self.assertQueryResult(query, [self.tag1_as_term_bookmarks])
def test_query_bookmarks_in_lax_mode_should_search_tags_as_terms(self): def test_query_bookmarks_in_lax_mode_should_search_tags_as_terms(self):
@@ -236,7 +230,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.profile.tag_search = UserProfile.TAG_SEARCH_LAX self.profile.tag_search = UserProfile.TAG_SEARCH_LAX
self.profile.save() self.profile.save()
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='tag1')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='tag1'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.tag1_bookmarks, self.tag1_bookmarks,
self.tag1_as_term_bookmarks, self.tag1_as_term_bookmarks,
@@ -244,17 +238,17 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.term1_tag1_bookmarks self.term1_tag1_bookmarks
]) ])
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='tag1 term1')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='tag1 term1'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.term1_tag1_bookmarks, self.term1_tag1_bookmarks,
]) ])
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='tag1 tag2')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='tag1 tag2'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.tag1_tag2_bookmarks, self.tag1_tag2_bookmarks,
]) ])
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='tag1 #tag2')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='tag1 #tag2'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.tag1_tag2_bookmarks, self.tag1_tag2_bookmarks,
]) ])
@@ -262,28 +256,28 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def test_query_bookmarks_should_return_no_matches(self): def test_query_bookmarks_should_return_no_matches(self):
self.setup_bookmark_search_data() self.setup_bookmark_search_data()
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='term3')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='term3'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='term1 term3')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='term1 term3'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='term1 #tag2')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='term1 #tag2'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='#tag3')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='#tag3'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
# Unused tag # Unused tag
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='#unused_tag1')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='#unused_tag1'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
# Unused tag combined with tag that is used # Unused tag combined with tag that is used
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='#tag1 #unused_tag1')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='#tag1 #unused_tag1'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
# Unused tag combined with term that is used # Unused tag combined with term that is used
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='term1 #unused_tag1')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='term1 #unused_tag1'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
def test_query_bookmarks_should_not_return_archived_bookmarks(self): def test_query_bookmarks_should_not_return_archived_bookmarks(self):
@@ -293,7 +287,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(is_archived=True) self.setup_bookmark(is_archived=True)
self.setup_bookmark(is_archived=True) self.setup_bookmark(is_archived=True)
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q=''))
self.assertQueryResult(query, [[bookmark1, bookmark2]]) self.assertQueryResult(query, [[bookmark1, bookmark2]])
@@ -304,7 +298,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark() self.setup_bookmark()
self.setup_bookmark() self.setup_bookmark()
query = queries.query_archived_bookmarks(self.user, self.profile, BookmarkSearch(query='')) query = queries.query_archived_bookmarks(self.user, self.profile, BookmarkSearch(q=''))
self.assertQueryResult(query, [[bookmark1, bookmark2]]) self.assertQueryResult(query, [[bookmark1, bookmark2]])
@@ -319,7 +313,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(user=other_user) self.setup_bookmark(user=other_user)
self.setup_bookmark(user=other_user) self.setup_bookmark(user=other_user)
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q=''))
self.assertQueryResult(query, [owned_bookmarks]) self.assertQueryResult(query, [owned_bookmarks])
@@ -334,7 +328,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(is_archived=True, user=other_user) self.setup_bookmark(is_archived=True, user=other_user)
self.setup_bookmark(is_archived=True, user=other_user) self.setup_bookmark(is_archived=True, user=other_user)
query = queries.query_archived_bookmarks(self.user, self.profile, BookmarkSearch(query='')) query = queries.query_archived_bookmarks(self.user, self.profile, BookmarkSearch(q=''))
self.assertQueryResult(query, [owned_bookmarks]) self.assertQueryResult(query, [owned_bookmarks])
@@ -344,7 +338,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(tags=[tag]) self.setup_bookmark(tags=[tag])
self.setup_bookmark(tags=[tag]) self.setup_bookmark(tags=[tag])
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='!untagged')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='!untagged'))
self.assertCountEqual(list(query), [untagged_bookmark]) self.assertCountEqual(list(query), [untagged_bookmark])
def test_query_bookmarks_untagged_should_be_combinable_with_search_terms(self): def test_query_bookmarks_untagged_should_be_combinable_with_search_terms(self):
@@ -353,7 +347,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(title='term2') self.setup_bookmark(title='term2')
self.setup_bookmark(tags=[tag]) self.setup_bookmark(tags=[tag])
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='!untagged term1')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='!untagged term1'))
self.assertCountEqual(list(query), [untagged_bookmark]) self.assertCountEqual(list(query), [untagged_bookmark])
def test_query_bookmarks_untagged_should_not_be_combinable_with_tags(self): def test_query_bookmarks_untagged_should_not_be_combinable_with_tags(self):
@@ -362,7 +356,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(tags=[tag]) self.setup_bookmark(tags=[tag])
self.setup_bookmark(tags=[tag]) self.setup_bookmark(tags=[tag])
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query=f'!untagged #{tag.name}')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q=f'!untagged #{tag.name}'))
self.assertCountEqual(list(query), []) self.assertCountEqual(list(query), [])
def test_query_archived_bookmarks_untagged_should_return_untagged_bookmarks_only(self): def test_query_archived_bookmarks_untagged_should_return_untagged_bookmarks_only(self):
@@ -371,7 +365,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(is_archived=True, tags=[tag]) self.setup_bookmark(is_archived=True, tags=[tag])
self.setup_bookmark(is_archived=True, tags=[tag]) self.setup_bookmark(is_archived=True, tags=[tag])
query = queries.query_archived_bookmarks(self.user, self.profile, BookmarkSearch(query='!untagged')) query = queries.query_archived_bookmarks(self.user, self.profile, BookmarkSearch(q='!untagged'))
self.assertCountEqual(list(query), [untagged_bookmark]) self.assertCountEqual(list(query), [untagged_bookmark])
def test_query_archived_bookmarks_untagged_should_be_combinable_with_search_terms(self): def test_query_archived_bookmarks_untagged_should_be_combinable_with_search_terms(self):
@@ -380,7 +374,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(is_archived=True, title='term2') self.setup_bookmark(is_archived=True, title='term2')
self.setup_bookmark(is_archived=True, tags=[tag]) self.setup_bookmark(is_archived=True, tags=[tag])
query = queries.query_archived_bookmarks(self.user, self.profile, BookmarkSearch(query='!untagged term1')) query = queries.query_archived_bookmarks(self.user, self.profile, BookmarkSearch(q='!untagged term1'))
self.assertCountEqual(list(query), [untagged_bookmark]) self.assertCountEqual(list(query), [untagged_bookmark])
def test_query_archived_bookmarks_untagged_should_not_be_combinable_with_tags(self): def test_query_archived_bookmarks_untagged_should_not_be_combinable_with_tags(self):
@@ -390,7 +384,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(is_archived=True, tags=[tag]) self.setup_bookmark(is_archived=True, tags=[tag])
query = queries.query_archived_bookmarks(self.user, self.profile, query = queries.query_archived_bookmarks(self.user, self.profile,
BookmarkSearch(query=f'!untagged #{tag.name}')) BookmarkSearch(q=f'!untagged #{tag.name}'))
self.assertCountEqual(list(query), []) self.assertCountEqual(list(query), [])
def test_query_bookmarks_unread_should_return_unread_bookmarks_only(self): def test_query_bookmarks_unread_should_return_unread_bookmarks_only(self):
@@ -398,7 +392,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
read_bookmarks = self.setup_numbered_bookmarks(5, unread=False) read_bookmarks = self.setup_numbered_bookmarks(5, unread=False)
# Legacy query filter # Legacy query filter
query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(query='!unread')) query = queries.query_bookmarks(self.user, self.profile, BookmarkSearch(q='!unread'))
self.assertCountEqual(list(query), unread_bookmarks) self.assertCountEqual(list(query), unread_bookmarks)
# Bookmark search filter - off # Bookmark search filter - off
@@ -421,7 +415,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
read_bookmarks = self.setup_numbered_bookmarks(5, unread=False, archived=True) read_bookmarks = self.setup_numbered_bookmarks(5, unread=False, archived=True)
# Legacy query filter # Legacy query filter
query = queries.query_archived_bookmarks(self.user, self.profile, BookmarkSearch(query='!unread')) query = queries.query_archived_bookmarks(self.user, self.profile, BookmarkSearch(q='!unread'))
self.assertCountEqual(list(query), unread_bookmarks) self.assertCountEqual(list(query), unread_bookmarks)
# Bookmark search filter - off # Bookmark search filter - off
@@ -461,7 +455,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def test_query_bookmark_tags_should_return_all_tags_for_empty_query(self): def test_query_bookmark_tags_should_return_all_tags_for_empty_query(self):
self.setup_tag_search_data() self.setup_tag_search_data()
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q=''))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.other_bookmarks), self.get_tags_from_bookmarks(self.other_bookmarks),
@@ -476,7 +470,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def test_query_bookmark_tags_should_search_single_term(self): def test_query_bookmark_tags_should_search_single_term(self):
self.setup_tag_search_data() self.setup_tag_search_data()
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='term1')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='term1'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.term1_bookmarks), self.get_tags_from_bookmarks(self.term1_bookmarks),
@@ -487,7 +481,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def test_query_bookmark_tags_should_search_multiple_terms(self): def test_query_bookmark_tags_should_search_multiple_terms(self):
self.setup_tag_search_data() self.setup_tag_search_data()
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='term2 term1')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='term2 term1'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.term1_term2_bookmarks), self.get_tags_from_bookmarks(self.term1_term2_bookmarks),
@@ -496,7 +490,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def test_query_bookmark_tags_should_search_single_tag(self): def test_query_bookmark_tags_should_search_single_tag(self):
self.setup_tag_search_data() self.setup_tag_search_data()
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='#tag1')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='#tag1'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.tag1_bookmarks), self.get_tags_from_bookmarks(self.tag1_bookmarks),
@@ -507,7 +501,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def test_query_bookmark_tags_should_search_multiple_tags(self): def test_query_bookmark_tags_should_search_multiple_tags(self):
self.setup_tag_search_data() self.setup_tag_search_data()
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='#tag1 #tag2')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='#tag1 #tag2'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks), self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks),
@@ -516,7 +510,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def test_query_bookmark_tags_should_search_multiple_tags_ignoring_casing(self): def test_query_bookmark_tags_should_search_multiple_tags_ignoring_casing(self):
self.setup_tag_search_data() self.setup_tag_search_data()
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='#Tag1 #TAG2')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='#Tag1 #TAG2'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks), self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks),
@@ -525,7 +519,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def test_query_bookmark_tags_should_search_term_and_tag_combined(self): def test_query_bookmark_tags_should_search_term_and_tag_combined(self):
self.setup_tag_search_data() self.setup_tag_search_data()
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='term1 #tag1')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='term1 #tag1'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.term1_tag1_bookmarks), self.get_tags_from_bookmarks(self.term1_tag1_bookmarks),
@@ -537,7 +531,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.profile.tag_search = UserProfile.TAG_SEARCH_STRICT self.profile.tag_search = UserProfile.TAG_SEARCH_STRICT
self.profile.save() self.profile.save()
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='tag1')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='tag1'))
self.assertQueryResult(query, self.get_tags_from_bookmarks(self.tag1_as_term_bookmarks)) self.assertQueryResult(query, self.get_tags_from_bookmarks(self.tag1_as_term_bookmarks))
def test_query_bookmark_tags_in_lax_mode_should_search_tags_as_terms(self): def test_query_bookmark_tags_in_lax_mode_should_search_tags_as_terms(self):
@@ -546,7 +540,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.profile.tag_search = UserProfile.TAG_SEARCH_LAX self.profile.tag_search = UserProfile.TAG_SEARCH_LAX
self.profile.save() self.profile.save()
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='tag1')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='tag1'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.tag1_bookmarks), self.get_tags_from_bookmarks(self.tag1_bookmarks),
self.get_tags_from_bookmarks(self.tag1_as_term_bookmarks), self.get_tags_from_bookmarks(self.tag1_as_term_bookmarks),
@@ -554,17 +548,17 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.get_tags_from_bookmarks(self.term1_tag1_bookmarks) self.get_tags_from_bookmarks(self.term1_tag1_bookmarks)
]) ])
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='tag1 term1')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='tag1 term1'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.term1_tag1_bookmarks), self.get_tags_from_bookmarks(self.term1_tag1_bookmarks),
]) ])
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='tag1 tag2')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='tag1 tag2'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks), self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks),
]) ])
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='tag1 #tag2')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='tag1 #tag2'))
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks), self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks),
]) ])
@@ -572,28 +566,28 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def test_query_bookmark_tags_should_return_no_matches(self): def test_query_bookmark_tags_should_return_no_matches(self):
self.setup_tag_search_data() self.setup_tag_search_data()
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='term3')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='term3'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='term1 term3')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='term1 term3'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='term1 #tag2')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='term1 #tag2'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='#tag3')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='#tag3'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
# Unused tag # Unused tag
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='#unused_tag1')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='#unused_tag1'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
# Unused tag combined with tag that is used # Unused tag combined with tag that is used
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='#tag1 #unused_tag1')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='#tag1 #unused_tag1'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
# Unused tag combined with term that is used # Unused tag combined with term that is used
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='term1 #unused_tag1')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='term1 #unused_tag1'))
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
def test_query_bookmark_tags_should_return_tags_for_unarchived_bookmarks_only(self): def test_query_bookmark_tags_should_return_tags_for_unarchived_bookmarks_only(self):
@@ -603,7 +597,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark() self.setup_bookmark()
self.setup_bookmark(is_archived=True, tags=[tag2]) self.setup_bookmark(is_archived=True, tags=[tag2])
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q=''))
self.assertQueryResult(query, [[tag1]]) self.assertQueryResult(query, [[tag1]])
@@ -613,7 +607,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(tags=[tag]) self.setup_bookmark(tags=[tag])
self.setup_bookmark(tags=[tag]) self.setup_bookmark(tags=[tag])
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q=''))
self.assertQueryResult(query, [[tag]]) self.assertQueryResult(query, [[tag]])
@@ -624,7 +618,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark() self.setup_bookmark()
self.setup_bookmark(is_archived=True, tags=[tag2]) self.setup_bookmark(is_archived=True, tags=[tag2])
query = queries.query_archived_bookmark_tags(self.user, self.profile, BookmarkSearch(query='')) query = queries.query_archived_bookmark_tags(self.user, self.profile, BookmarkSearch(q=''))
self.assertQueryResult(query, [[tag2]]) self.assertQueryResult(query, [[tag2]])
@@ -634,7 +628,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(is_archived=True, tags=[tag]) self.setup_bookmark(is_archived=True, tags=[tag])
self.setup_bookmark(is_archived=True, tags=[tag]) self.setup_bookmark(is_archived=True, tags=[tag])
query = queries.query_archived_bookmark_tags(self.user, self.profile, BookmarkSearch(query='')) query = queries.query_archived_bookmark_tags(self.user, self.profile, BookmarkSearch(q=''))
self.assertQueryResult(query, [[tag]]) self.assertQueryResult(query, [[tag]])
@@ -649,7 +643,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(user=other_user, tags=[self.setup_tag(user=other_user)]) self.setup_bookmark(user=other_user, tags=[self.setup_tag(user=other_user)])
self.setup_bookmark(user=other_user, tags=[self.setup_tag(user=other_user)]) self.setup_bookmark(user=other_user, tags=[self.setup_tag(user=other_user)])
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q=''))
self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)]) self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)])
@@ -664,7 +658,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(is_archived=True, user=other_user, tags=[self.setup_tag(user=other_user)]) self.setup_bookmark(is_archived=True, user=other_user, tags=[self.setup_tag(user=other_user)])
self.setup_bookmark(is_archived=True, user=other_user, tags=[self.setup_tag(user=other_user)]) self.setup_bookmark(is_archived=True, user=other_user, tags=[self.setup_tag(user=other_user)])
query = queries.query_archived_bookmark_tags(self.user, self.profile, BookmarkSearch(query='')) query = queries.query_archived_bookmark_tags(self.user, self.profile, BookmarkSearch(q=''))
self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)]) self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)])
@@ -675,13 +669,13 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(title='term1', tags=[tag]) self.setup_bookmark(title='term1', tags=[tag])
self.setup_bookmark(tags=[tag]) self.setup_bookmark(tags=[tag])
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='!untagged')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='!untagged'))
self.assertCountEqual(list(query), []) self.assertCountEqual(list(query), [])
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='!untagged term1')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='!untagged term1'))
self.assertCountEqual(list(query), []) self.assertCountEqual(list(query), [])
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query=f'!untagged #{tag.name}')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q=f'!untagged #{tag.name}'))
self.assertCountEqual(list(query), []) self.assertCountEqual(list(query), [])
def test_query_archived_bookmark_tags_untagged_should_never_return_any_tags(self): def test_query_archived_bookmark_tags_untagged_should_never_return_any_tags(self):
@@ -691,14 +685,14 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(is_archived=True, title='term1', tags=[tag]) self.setup_bookmark(is_archived=True, title='term1', tags=[tag])
self.setup_bookmark(is_archived=True, tags=[tag]) self.setup_bookmark(is_archived=True, tags=[tag])
query = queries.query_archived_bookmark_tags(self.user, self.profile, BookmarkSearch(query='!untagged')) query = queries.query_archived_bookmark_tags(self.user, self.profile, BookmarkSearch(q='!untagged'))
self.assertCountEqual(list(query), []) self.assertCountEqual(list(query), [])
query = queries.query_archived_bookmark_tags(self.user, self.profile, BookmarkSearch(query='!untagged term1')) query = queries.query_archived_bookmark_tags(self.user, self.profile, BookmarkSearch(q='!untagged term1'))
self.assertCountEqual(list(query), []) self.assertCountEqual(list(query), [])
query = queries.query_archived_bookmark_tags(self.user, self.profile, query = queries.query_archived_bookmark_tags(self.user, self.profile,
BookmarkSearch(query=f'!untagged #{tag.name}')) BookmarkSearch(q=f'!untagged #{tag.name}'))
self.assertCountEqual(list(query), []) self.assertCountEqual(list(query), [])
def test_query_bookmark_tags_filter_unread(self): def test_query_bookmark_tags_filter_unread(self):
@@ -708,7 +702,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
read_tags = self.get_tags_from_bookmarks(read_bookmarks) read_tags = self.get_tags_from_bookmarks(read_bookmarks)
# Legacy query filter # Legacy query filter
query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(query='!unread')) query = queries.query_bookmark_tags(self.user, self.profile, BookmarkSearch(q='!unread'))
self.assertCountEqual(list(query), unread_tags) self.assertCountEqual(list(query), unread_tags)
# Bookmark search filter - off # Bookmark search filter - off
@@ -769,14 +763,14 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(user=user4, shared=True, tags=[tag]), self.setup_bookmark(user=user4, shared=True, tags=[tag]),
# Should return shared bookmarks from all users # Should return shared bookmarks from all users
query_set = queries.query_shared_bookmarks(None, self.profile, BookmarkSearch(query=''), False) query_set = queries.query_shared_bookmarks(None, self.profile, BookmarkSearch(q=''), False)
self.assertQueryResult(query_set, [shared_bookmarks]) self.assertQueryResult(query_set, [shared_bookmarks])
# Should respect search query # Should respect search query
query_set = queries.query_shared_bookmarks(None, self.profile, BookmarkSearch(query='test title'), False) query_set = queries.query_shared_bookmarks(None, self.profile, BookmarkSearch(q='test title'), False)
self.assertQueryResult(query_set, [[shared_bookmarks[0]]]) self.assertQueryResult(query_set, [[shared_bookmarks[0]]])
query_set = queries.query_shared_bookmarks(None, self.profile, BookmarkSearch(query=f'#{tag.name}'), False) query_set = queries.query_shared_bookmarks(None, self.profile, BookmarkSearch(q=f'#{tag.name}'), False)
self.assertQueryResult(query_set, [[shared_bookmarks[2]]]) self.assertQueryResult(query_set, [[shared_bookmarks[2]]])
def test_query_publicly_shared_bookmarks(self): def test_query_publicly_shared_bookmarks(self):
@@ -786,7 +780,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
bookmark1 = self.setup_bookmark(user=user1, shared=True) bookmark1 = self.setup_bookmark(user=user1, shared=True)
self.setup_bookmark(user=user2, shared=True) self.setup_bookmark(user=user2, shared=True)
query_set = queries.query_shared_bookmarks(None, self.profile, BookmarkSearch(query=''), True) query_set = queries.query_shared_bookmarks(None, self.profile, BookmarkSearch(q=''), True)
self.assertQueryResult(query_set, [[bookmark1]]) self.assertQueryResult(query_set, [[bookmark1]])
def test_query_shared_bookmark_tags(self): def test_query_shared_bookmark_tags(self):
@@ -810,7 +804,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(user=user3, shared=False, tags=[self.setup_tag(user=user3)]), self.setup_bookmark(user=user3, shared=False, tags=[self.setup_tag(user=user3)]),
self.setup_bookmark(user=user4, shared=True, tags=[self.setup_tag(user=user4)]), self.setup_bookmark(user=user4, shared=True, tags=[self.setup_tag(user=user4)]),
query_set = queries.query_shared_bookmark_tags(None, self.profile, BookmarkSearch(query=''), False) query_set = queries.query_shared_bookmark_tags(None, self.profile, BookmarkSearch(q=''), False)
self.assertQueryResult(query_set, [shared_tags]) self.assertQueryResult(query_set, [shared_tags])
@@ -824,7 +818,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(user=user1, shared=True, tags=[tag1]), self.setup_bookmark(user=user1, shared=True, tags=[tag1]),
self.setup_bookmark(user=user2, shared=True, tags=[tag2]), self.setup_bookmark(user=user2, shared=True, tags=[tag2]),
query_set = queries.query_shared_bookmark_tags(None, self.profile, BookmarkSearch(query=''), True) query_set = queries.query_shared_bookmark_tags(None, self.profile, BookmarkSearch(q=''), True)
self.assertQueryResult(query_set, [[tag1]]) self.assertQueryResult(query_set, [[tag1]])
@@ -849,11 +843,11 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(user=users_without_shared_bookmarks[2], shared=True), self.setup_bookmark(user=users_without_shared_bookmarks[2], shared=True),
# Should return users with shared bookmarks # Should return users with shared bookmarks
query_set = queries.query_shared_bookmark_users(self.profile, BookmarkSearch(query=''), False) query_set = queries.query_shared_bookmark_users(self.profile, BookmarkSearch(q=''), False)
self.assertQueryResult(query_set, [users_with_shared_bookmarks]) self.assertQueryResult(query_set, [users_with_shared_bookmarks])
# Should respect search query # Should respect search query
query_set = queries.query_shared_bookmark_users(self.profile, BookmarkSearch(query='test title'), False) query_set = queries.query_shared_bookmark_users(self.profile, BookmarkSearch(q='test title'), False)
self.assertQueryResult(query_set, [[users_with_shared_bookmarks[0]]]) self.assertQueryResult(query_set, [[users_with_shared_bookmarks[0]]])
def test_query_publicly_shared_bookmark_users(self): def test_query_publicly_shared_bookmark_users(self):
@@ -863,7 +857,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(user=user1, shared=True) self.setup_bookmark(user=user1, shared=True)
self.setup_bookmark(user=user2, shared=True) self.setup_bookmark(user=user2, shared=True)
query_set = queries.query_shared_bookmark_users(self.profile, BookmarkSearch(query=''), True) query_set = queries.query_shared_bookmark_users(self.profile, BookmarkSearch(q=''), True)
self.assertQueryResult(query_set, [[user1]]) self.assertQueryResult(query_set, [[user1]])
def test_sorty_by_date_added_asc(self): def test_sorty_by_date_added_asc(self):

View File

@@ -12,7 +12,7 @@ class UserSelectTagTest(TestCase, BookmarkFactoryMixin):
request = rf.get(url) request = rf.get(url)
request.user = self.get_or_create_test_user() request.user = self.get_or_create_test_user()
request.user_profile = self.get_or_create_test_user().profile request.user_profile = self.get_or_create_test_user().profile
search = BookmarkSearch.from_request(request) search = BookmarkSearch.from_request(request.GET)
context = RequestContext(request, { context = RequestContext(request, {
'request': request, 'request': request,
'search': search, 'search': search,
@@ -82,11 +82,11 @@ class UserSelectTagTest(TestCase, BookmarkFactoryMixin):
self.assertNoHiddenInput(rendered_template, 'unread') self.assertNoHiddenInput(rendered_template, 'unread')
# With params # With params
url = '/test?q=foo&user=john&sort=title_asc&shared=shared&unread=yes' url = '/test?q=foo&user=john&sort=title_asc&shared=yes&unread=yes'
rendered_template = self.render_template(url) rendered_template = self.render_template(url)
self.assertNoHiddenInput(rendered_template, 'user') self.assertNoHiddenInput(rendered_template, 'user')
self.assertHiddenInput(rendered_template, 'q', 'foo') self.assertHiddenInput(rendered_template, 'q', 'foo')
self.assertHiddenInput(rendered_template, 'sort', 'title_asc') self.assertHiddenInput(rendered_template, 'sort', 'title_asc')
self.assertHiddenInput(rendered_template, 'shared', 'shared') self.assertHiddenInput(rendered_template, 'shared', 'yes')
self.assertHiddenInput(rendered_template, 'unread', 'yes') self.assertHiddenInput(rendered_template, 'unread', 'yes')

View File

@@ -1,6 +1,8 @@
import urllib.parse
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http import HttpResponseRedirect, Http404, HttpResponseBadRequest from django.http import HttpResponseRedirect, Http404, HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
@@ -17,6 +19,9 @@ _default_page_size = 30
@login_required @login_required
def index(request): def index(request):
if request.method == 'POST':
return search_action(request)
bookmark_list = contexts.ActiveBookmarkListContext(request) bookmark_list = contexts.ActiveBookmarkListContext(request)
tag_cloud = contexts.ActiveTagCloudContext(request) tag_cloud = contexts.ActiveTagCloudContext(request)
return render(request, 'bookmarks/index.html', { return render(request, 'bookmarks/index.html', {
@@ -27,6 +32,9 @@ def index(request):
@login_required @login_required
def archived(request): def archived(request):
if request.method == 'POST':
return search_action(request)
bookmark_list = contexts.ArchivedBookmarkListContext(request) bookmark_list = contexts.ArchivedBookmarkListContext(request)
tag_cloud = contexts.ArchivedTagCloudContext(request) tag_cloud = contexts.ArchivedTagCloudContext(request)
return render(request, 'bookmarks/archive.html', { return render(request, 'bookmarks/archive.html', {
@@ -36,11 +44,13 @@ def archived(request):
def shared(request): def shared(request):
search = BookmarkSearch.from_request(request) if request.method == 'POST':
return search_action(request)
bookmark_list = contexts.SharedBookmarkListContext(request) bookmark_list = contexts.SharedBookmarkListContext(request)
tag_cloud = contexts.SharedTagCloudContext(request) tag_cloud = contexts.SharedTagCloudContext(request)
public_only = not request.user.is_authenticated public_only = not request.user.is_authenticated
users = queries.query_shared_bookmark_users(request.user_profile, search, public_only) users = queries.query_shared_bookmark_users(request.user_profile, bookmark_list.search, public_only)
return render(request, 'bookmarks/shared.html', { return render(request, 'bookmarks/shared.html', {
'bookmark_list': bookmark_list, 'bookmark_list': bookmark_list,
'tag_cloud': tag_cloud, 'tag_cloud': tag_cloud,
@@ -48,6 +58,23 @@ def shared(request):
}) })
def search_action(request):
if 'save' in request.POST:
if not request.user.is_authenticated:
return HttpResponseForbidden()
search = BookmarkSearch.from_request(request.POST)
request.user_profile.search_preferences = search.preferences_dict
request.user_profile.save()
# redirect to base url including new query params
search = BookmarkSearch.from_request(request.POST, request.user_profile.search_preferences)
base_url = request.path
query_params = search.query_params
query_string = urllib.parse.urlencode(query_params)
url = base_url if not query_string else base_url + '?' + query_string
return HttpResponseRedirect(url)
def convert_tag_string(tag_string: str): def convert_tag_string(tag_string: str):
# Tag strings coming from inputs are space-separated, however services.bookmarks functions expect comma-separated # Tag strings coming from inputs are space-separated, however services.bookmarks functions expect comma-separated
# strings # strings
@@ -169,14 +196,14 @@ def mark_as_read(request, bookmark_id: int):
@login_required @login_required
def index_action(request): def index_action(request):
search = BookmarkSearch.from_request(request) search = BookmarkSearch.from_request(request.GET)
query = queries.query_bookmarks(request.user, request.user_profile, search) query = queries.query_bookmarks(request.user, request.user_profile, search)
return action(request, query) return action(request, query)
@login_required @login_required
def archived_action(request): def archived_action(request):
search = BookmarkSearch.from_request(request) search = BookmarkSearch.from_request(request.GET)
query = queries.query_archived_bookmarks(request.user, request.user_profile, search) query = queries.query_archived_bookmarks(request.user, request.user_profile, search)
return action(request, query) return action(request, query)

View File

@@ -54,11 +54,12 @@ class BookmarkItem:
class BookmarkListContext: class BookmarkListContext:
def __init__(self, request: WSGIRequest) -> None: def __init__(self, request: WSGIRequest) -> None:
self.request = request
self.search = BookmarkSearch.from_request(self.request)
user = request.user user = request.user
user_profile = request.user_profile user_profile = request.user_profile
self.request = request
self.search = BookmarkSearch.from_request(self.request.GET, user_profile.search_preferences)
query_set = self.get_bookmark_query_set() query_set = self.get_bookmark_query_set()
page_number = request.GET.get('page') page_number = request.GET.get('page')
paginator = Paginator(query_set, DEFAULT_PAGE_SIZE) paginator = Paginator(query_set, DEFAULT_PAGE_SIZE)
@@ -175,8 +176,10 @@ class TagGroup:
class TagCloudContext: class TagCloudContext:
def __init__(self, request: WSGIRequest) -> None: def __init__(self, request: WSGIRequest) -> None:
user_profile = request.user_profile
self.request = request self.request = request
self.search = BookmarkSearch.from_request(self.request) self.search = BookmarkSearch.from_request(self.request.GET, user_profile.search_preferences)
query_set = self.get_tag_query_set() query_set = self.get_tag_query_set()
tags = list(query_set) tags = list(query_set)
@@ -196,7 +199,7 @@ class TagCloudContext:
raise Exception(f'Must be implemented by subclass') raise Exception(f'Must be implemented by subclass')
def get_selected_tags(self, tags: List[Tag]): def get_selected_tags(self, tags: List[Tag]):
parsed_query = queries.parse_query_string(self.search.query) parsed_query = queries.parse_query_string(self.search.q)
tag_names = parsed_query['tag_names'] tag_names = parsed_query['tag_names']
if self.request.user_profile.tag_search == UserProfile.TAG_SEARCH_LAX: if self.request.user_profile.tag_search == UserProfile.TAG_SEARCH_LAX:
tag_names = tag_names + parsed_query['search_terms'] tag_names = tag_names + parsed_query['search_terms']