Allow searching for tags without hash character (#449)

* Allow searching for tags without hash character

* Allow removing selected tags without hash

* Add more tests
This commit is contained in:
Sascha Ißbrücker
2023-05-18 09:06:22 +02:00
committed by GitHub
parent e9061f373a
commit 3af4e07eb6
16 changed files with 367 additions and 137 deletions

View File

@@ -23,7 +23,7 @@ class BookmarkViewSet(viewsets.GenericViewSet,
# 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':
query_string = self.request.GET.get('q') query_string = self.request.GET.get('q')
return queries.query_bookmarks(user, query_string) return queries.query_bookmarks(user, user.profile, query_string)
# For single entity actions use default query set without projections # For single entity actions use default query set without projections
return Bookmark.objects.all().filter(owner=user) return Bookmark.objects.all().filter(owner=user)
@@ -35,7 +35,7 @@ class BookmarkViewSet(viewsets.GenericViewSet,
def archived(self, request): def archived(self, request):
user = request.user user = request.user
query_string = request.GET.get('q') query_string = request.GET.get('q')
query_set = queries.query_archived_bookmarks(user, query_string) query_set = queries.query_archived_bookmarks(user, user.profile, query_string)
page = self.paginate_queryset(query_set) page = self.paginate_queryset(query_set)
serializer = self.get_serializer_class() serializer = self.get_serializer_class()
data = serializer(page, many=True).data data = serializer(page, many=True).data
@@ -45,7 +45,7 @@ class BookmarkViewSet(viewsets.GenericViewSet,
def shared(self, request): def shared(self, request):
filters = BookmarkFilters(request) filters = BookmarkFilters(request)
user = User.objects.filter(username=filters.user).first() user = User.objects.filter(username=filters.user).first()
query_set = queries.query_shared_bookmarks(user, filters.query) query_set = queries.query_shared_bookmarks(user, request.user.profile, filters.query)
page = self.paginate_queryset(query_set) page = self.paginate_queryset(query_set)
serializer = self.get_serializer_class() serializer = self.get_serializer_class()
data = serializer(page, many=True).data data = serializer(page, many=True).data

View File

@@ -18,7 +18,7 @@ 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)
query_string = request.GET.get('q') query_string = request.GET.get('q')
query_set = queries.query_bookmarks(feed_token.user, query_string) query_set = queries.query_bookmarks(feed_token.user, feed_token.user.profile, query_string)
return FeedContext(feed_token, query_set) return FeedContext(feed_token, query_set)
def item_title(self, item: Bookmark): def item_title(self, item: Bookmark):

View File

@@ -0,0 +1,18 @@
# Generated by Django 4.1.7 on 2023-04-10 01:55
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('bookmarks', '0019_userprofile_enable_favicons'),
]
operations = [
migrations.AddField(
model_name='userprofile',
name='tag_search',
field=models.CharField(choices=[('strict', 'Strict'), ('lax', 'Lax')], default='strict', max_length=10),
),
]

View File

@@ -153,6 +153,12 @@ class UserProfile(models.Model):
(WEB_ARCHIVE_INTEGRATION_DISABLED, 'Disabled'), (WEB_ARCHIVE_INTEGRATION_DISABLED, 'Disabled'),
(WEB_ARCHIVE_INTEGRATION_ENABLED, 'Enabled'), (WEB_ARCHIVE_INTEGRATION_ENABLED, 'Enabled'),
] ]
TAG_SEARCH_STRICT = 'strict'
TAG_SEARCH_LAX = 'lax'
TAG_SEARCH_CHOICES = [
(TAG_SEARCH_STRICT, 'Strict'),
(TAG_SEARCH_LAX, 'Lax'),
]
user = models.OneToOneField(get_user_model(), related_name='profile', on_delete=models.CASCADE) user = models.OneToOneField(get_user_model(), related_name='profile', on_delete=models.CASCADE)
theme = models.CharField(max_length=10, choices=THEME_CHOICES, blank=False, default=THEME_AUTO) theme = models.CharField(max_length=10, choices=THEME_CHOICES, blank=False, default=THEME_AUTO)
bookmark_date_display = models.CharField(max_length=10, choices=BOOKMARK_DATE_DISPLAY_CHOICES, blank=False, bookmark_date_display = models.CharField(max_length=10, choices=BOOKMARK_DATE_DISPLAY_CHOICES, blank=False,
@@ -161,6 +167,8 @@ class UserProfile(models.Model):
default=BOOKMARK_LINK_TARGET_BLANK) default=BOOKMARK_LINK_TARGET_BLANK)
web_archive_integration = models.CharField(max_length=10, choices=WEB_ARCHIVE_INTEGRATION_CHOICES, blank=False, web_archive_integration = models.CharField(max_length=10, choices=WEB_ARCHIVE_INTEGRATION_CHOICES, blank=False,
default=WEB_ARCHIVE_INTEGRATION_DISABLED) default=WEB_ARCHIVE_INTEGRATION_DISABLED)
tag_search = models.CharField(max_length=10, choices=TAG_SEARCH_CHOICES, blank=False,
default=TAG_SEARCH_STRICT)
enable_sharing = models.BooleanField(default=False, null=False) enable_sharing = models.BooleanField(default=False, null=False)
enable_favicons = models.BooleanField(default=False, null=False) enable_favicons = models.BooleanField(default=False, null=False)
@@ -168,7 +176,8 @@ class UserProfile(models.Model):
class UserProfileForm(forms.ModelForm): class UserProfileForm(forms.ModelForm):
class Meta: class Meta:
model = UserProfile model = UserProfile
fields = ['theme', 'bookmark_date_display', 'bookmark_link_target', 'web_archive_integration', 'enable_sharing', 'enable_favicons'] fields = ['theme', 'bookmark_date_display', 'bookmark_link_target', 'web_archive_integration', 'tag_search',
'enable_sharing', 'enable_favicons']
@receiver(post_save, sender=get_user_model()) @receiver(post_save, sender=get_user_model())

View File

@@ -1,29 +1,29 @@
from typing import Optional from typing import Optional
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import Q, QuerySet from django.db.models import Q, QuerySet, Exists, OuterRef
from bookmarks.models import Bookmark, Tag from bookmarks.models import Bookmark, Tag, UserProfile
from bookmarks.utils import unique from bookmarks.utils import unique
def query_bookmarks(user: User, query_string: str) -> QuerySet: def query_bookmarks(user: User, profile: UserProfile, query_string: str) -> QuerySet:
return _base_bookmarks_query(user, query_string) \ return _base_bookmarks_query(user, profile, query_string) \
.filter(is_archived=False) .filter(is_archived=False)
def query_archived_bookmarks(user: User, query_string: str) -> QuerySet: def query_archived_bookmarks(user: User, profile: UserProfile, query_string: str) -> QuerySet:
return _base_bookmarks_query(user, query_string) \ return _base_bookmarks_query(user, profile, query_string) \
.filter(is_archived=True) .filter(is_archived=True)
def query_shared_bookmarks(user: Optional[User], query_string: str) -> QuerySet: def query_shared_bookmarks(user: Optional[User], profile: UserProfile, query_string: str) -> QuerySet:
return _base_bookmarks_query(user, query_string) \ return _base_bookmarks_query(user, profile, query_string) \
.filter(shared=True) \ .filter(shared=True) \
.filter(owner__profile__enable_sharing=True) .filter(owner__profile__enable_sharing=True)
def _base_bookmarks_query(user: Optional[User], query_string: str) -> QuerySet: def _base_bookmarks_query(user: Optional[User], profile: UserProfile, query_string: str) -> QuerySet:
query_set = Bookmark.objects query_set = Bookmark.objects
# Filter for user # Filter for user
@@ -35,13 +35,16 @@ def _base_bookmarks_query(user: Optional[User], query_string: str) -> QuerySet:
# Filter for search terms and tags # Filter for search terms and tags
for term in query['search_terms']: for term in query['search_terms']:
query_set = query_set.filter( conditions = Q(title__icontains=term) \
Q(title__icontains=term) | Q(description__icontains=term) \
| Q(description__icontains=term) | Q(website_title__icontains=term) \
| Q(website_title__icontains=term) | Q(website_description__icontains=term) \
| Q(website_description__icontains=term) | Q(url__icontains=term)
| Q(url__icontains=term)
) if profile.tag_search == UserProfile.TAG_SEARCH_LAX:
conditions = conditions | Exists(Bookmark.objects.filter(id=OuterRef('id'), tags__name__iexact=term))
query_set = query_set.filter(conditions)
for tag_name in query['tag_names']: for tag_name in query['tag_names']:
query_set = query_set.filter( query_set = query_set.filter(
@@ -65,32 +68,32 @@ def _base_bookmarks_query(user: Optional[User], query_string: str) -> QuerySet:
return query_set return query_set
def query_bookmark_tags(user: User, query_string: str) -> QuerySet: def query_bookmark_tags(user: User, profile: UserProfile, query_string: str) -> QuerySet:
bookmarks_query = query_bookmarks(user, query_string) bookmarks_query = query_bookmarks(user, profile, query_string)
query_set = Tag.objects.filter(bookmark__in=bookmarks_query) query_set = Tag.objects.filter(bookmark__in=bookmarks_query)
return query_set.distinct() return query_set.distinct()
def query_archived_bookmark_tags(user: User, query_string: str) -> QuerySet: def query_archived_bookmark_tags(user: User, profile: UserProfile, query_string: str) -> QuerySet:
bookmarks_query = query_archived_bookmarks(user, query_string) bookmarks_query = query_archived_bookmarks(user, profile, query_string)
query_set = Tag.objects.filter(bookmark__in=bookmarks_query) query_set = Tag.objects.filter(bookmark__in=bookmarks_query)
return query_set.distinct() return query_set.distinct()
def query_shared_bookmark_tags(user: Optional[User], query_string: str) -> QuerySet: def query_shared_bookmark_tags(user: Optional[User], profile: UserProfile, query_string: str) -> QuerySet:
bookmarks_query = query_shared_bookmarks(user, query_string) bookmarks_query = query_shared_bookmarks(user, profile, query_string)
query_set = Tag.objects.filter(bookmark__in=bookmarks_query) query_set = Tag.objects.filter(bookmark__in=bookmarks_query)
return query_set.distinct() return query_set.distinct()
def query_shared_bookmark_users(query_string: str) -> QuerySet: def query_shared_bookmark_users(profile: UserProfile, query_string: str) -> QuerySet:
bookmarks_query = query_shared_bookmarks(None, query_string) bookmarks_query = query_shared_bookmarks(None, profile, query_string)
query_set = User.objects.filter(bookmark__in=bookmarks_query) query_set = User.objects.filter(bookmark__in=bookmarks_query)

View File

@@ -22,7 +22,7 @@
{% if bookmark.tag_names %} {% if bookmark.tag_names %}
<span> <span>
{% for tag_name in bookmark.tag_names %} {% for tag_name in bookmark.tag_names %}
<a href="?{% append_to_query_param q=tag_name|hash_tag %}">{{ tag_name|hash_tag }}</a> <a href="?{% add_tag_to_query tag_name %}">{{ tag_name|hash_tag }}</a>
{% endfor %} {% endfor %}
</span> </span>
{% endif %} {% endif %}

View File

@@ -4,7 +4,7 @@
{% if has_selected_tags %} {% if has_selected_tags %}
<p class="selected-tags"> <p class="selected-tags">
{% for tag in selected_tags %} {% for tag in selected_tags %}
<a href="?{% remove_from_query_param q=tag.name|hash_tag %}" <a href="?{% remove_tag_from_query tag.name %}"
class="text-bold mr-2"> class="text-bold mr-2">
<span>-{{ tag.name }}</span> <span>-{{ tag.name }}</span>
</a> </a>
@@ -17,14 +17,14 @@
{% for tag in group.tags %} {% for tag in group.tags %}
{# Highlight first char of first tag in group #} {# Highlight first char of first tag in group #}
{% if forloop.counter == 1 %} {% if forloop.counter == 1 %}
<a href="?{% append_to_query_param q=tag.name|hash_tag %}" <a href="?{% add_tag_to_query tag.name %}"
class="mr-2" data-is-tag-item> class="mr-2" data-is-tag-item>
<span <span
class="highlight-char">{{ tag.name|first_char }}</span><span>{{ tag.name|remaining_chars:1 }}</span> class="highlight-char">{{ tag.name|first_char }}</span><span>{{ tag.name|remaining_chars:1 }}</span>
</a> </a>
{% else %} {% else %}
{# Render remaining tags normally #} {# Render remaining tags normally #}
<a href="?{% append_to_query_param q=tag.name|hash_tag %}" <a href="?{% add_tag_to_query tag.name %}"
class="mr-2" data-is-tag-item> class="mr-2" data-is-tag-item>
<span>{{ tag.name }}</span> <span>{{ tag.name }}</span>
</a> </a>

View File

@@ -36,6 +36,15 @@
Whether to open bookmarks a new page or in the same page. Whether to open bookmarks a new page or in the same page.
</div> </div>
</div> </div>
<div class="form-group">
<label for="{{ form.tag_search.id_for_label }}" class="form-label">Tag search</label>
{{ form.tag_search|add_class:"form-select col-2 col-sm-12" }}
<div class="form-input-hint">
In strict mode, tags must be prefixed with a hash character (#).
In lax mode, tags can also be searched without the hash character.
Note that tags without the hash character are indistinguishable from search terms, which means the search result will also include bookmarks where a search term matches otherwise.
</div>
</div>
<div class="form-group"> <div class="form-group">
<label for="{{ form.enable_favicons.id_for_label }}" class="form-checkbox"> <label for="{{ form.enable_favicons.id_for_label }}" class="form-checkbox">
{{ form.enable_favicons }} {{ form.enable_favicons }}

View File

@@ -3,6 +3,7 @@ import re
from django import template from django import template
from bookmarks import utils from bookmarks import utils
from bookmarks.models import UserProfile
register = template.Library() register = template.Library()
@@ -19,36 +20,39 @@ def update_query_string(context, **kwargs):
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def append_to_query_param(context, **kwargs): def add_tag_to_query(context, tag_name: str):
query = context.request.GET.copy() params = context.request.GET.copy()
# Append to or create query param # Append to or create query string
for key in kwargs: if params.__contains__('q'):
if query.__contains__(key): query_string = params.__getitem__('q') + ' '
value = query.__getitem__(key) + ' ' else:
else: query_string = ''
value = '' query_string = query_string + '#' + tag_name
value = value + kwargs[key] params.__setitem__('q', query_string)
query.__setitem__(key, value)
return query.urlencode() return params.urlencode()
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def remove_from_query_param(context, **kwargs): def remove_tag_from_query(context, tag_name: str):
query = context.request.GET.copy() params = context.request.GET.copy()
if params.__contains__('q'):
# Split query string into parts
query_string = params.__getitem__('q')
query_parts = query_string.split()
# Remove tag with hash
tag_name_with_hash = '#' + tag_name
query_parts = [part for part in query_parts if str.lower(part) != str.lower(tag_name_with_hash)]
# When using lax tag search, also remove tag without hash
profile = context.request.user.profile
if profile.tag_search == UserProfile.TAG_SEARCH_LAX:
query_parts = [part for part in query_parts if str.lower(part) != str.lower(tag_name)]
# Rebuild query string
query_string = ' '.join(query_parts)
params.__setitem__('q', query_string)
# Remove item from query param return params.urlencode()
for key in kwargs:
if query.__contains__(key):
value = query.__getitem__(key)
parts = value.split()
part_to_remove = kwargs[key]
updated_parts = [part for part in parts if str.lower(part) != str.lower(part_to_remove)]
updated_value = ' '.join(updated_parts)
query.__setitem__(key, updated_value)
return query.urlencode()
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)

View File

@@ -158,6 +158,37 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
self.assertSelectedTags(response, [tags[0], tags[1]]) self.assertSelectedTags(response, [tags[0], tags[1]])
def test_should_not_display_search_terms_from_query_as_selected_tags_in_strict_mode(self):
tags = [
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
]
self.setup_bookmark(title=tags[0].name, tags=tags, is_archived=True)
response = self.client.get(reverse('bookmarks:archived') + f'?q={tags[0].name}+%23{tags[1].name.upper()}')
self.assertSelectedTags(response, [tags[1]])
def test_should_display_search_terms_from_query_as_selected_tags_in_lax_mode(self):
self.user.profile.tag_search = UserProfile.TAG_SEARCH_LAX
self.user.profile.save()
tags = [
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
]
self.setup_bookmark(tags=tags, is_archived=True)
response = self.client.get(reverse('bookmarks:archived') + f'?q={tags[0].name}+%23{tags[1].name.upper()}')
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_bookmark(is_archived=True), self.setup_bookmark(is_archived=True),

View File

@@ -155,7 +155,38 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
] ]
self.setup_bookmark(tags=tags) self.setup_bookmark(tags=tags)
response = self.client.get(reverse('bookmarks:index') + f'?q=%23{tags[0].name}+%23{tags[1].name}') response = self.client.get(reverse('bookmarks:index') + f'?q=%23{tags[0].name}+%23{tags[1].name.upper()}')
self.assertSelectedTags(response, [tags[0], tags[1]])
def test_should_not_display_search_terms_from_query_as_selected_tags_in_strict_mode(self):
tags = [
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
]
self.setup_bookmark(title=tags[0].name, tags=tags)
response = self.client.get(reverse('bookmarks:index') + f'?q={tags[0].name}+%23{tags[1].name.upper()}')
self.assertSelectedTags(response, [tags[1]])
def test_should_display_search_terms_from_query_as_selected_tags_in_lax_mode(self):
self.user.profile.tag_search = UserProfile.TAG_SEARCH_LAX
self.user.profile.save()
tags = [
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
self.setup_tag(),
]
self.setup_bookmark(tags=tags)
response = self.client.get(reverse('bookmarks:index') + f'?q={tags[0].name}+%23{tags[1].name.upper()}')
self.assertSelectedTags(response, [tags[0], tags[1]]) self.assertSelectedTags(response, [tags[0], tags[1]])

View File

@@ -5,7 +5,7 @@ from django.db.models import QuerySet
from django.test import TestCase from django.test import TestCase
from bookmarks import queries from bookmarks import queries
from bookmarks.models import Bookmark from bookmarks.models import Bookmark, UserProfile
from bookmarks.tests.helpers import BookmarkFactoryMixin, random_sentence from bookmarks.tests.helpers import BookmarkFactoryMixin, random_sentence
from bookmarks.utils import unique from bookmarks.utils import unique
@@ -13,6 +13,8 @@ User = get_user_model()
class QueriesTestCase(TestCase, BookmarkFactoryMixin): class QueriesTestCase(TestCase, BookmarkFactoryMixin):
def setUp(self):
self.profile = self.get_or_create_test_user().profile
def setup_bookmark_search_data(self) -> None: def setup_bookmark_search_data(self) -> None:
tag1 = self.setup_tag(name='tag1') tag1 = self.setup_tag(name='tag1')
@@ -53,6 +55,13 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(website_title=random_sentence(), tags=[tag1]), self.setup_bookmark(website_title=random_sentence(), tags=[tag1]),
self.setup_bookmark(website_description=random_sentence(), tags=[tag1]), self.setup_bookmark(website_description=random_sentence(), tags=[tag1]),
] ]
self.tag1_as_term_bookmarks = [
self.setup_bookmark(url='http://example.com/tag1'),
self.setup_bookmark(title=random_sentence(including_word='tag1')),
self.setup_bookmark(description=random_sentence(including_word='tag1')),
self.setup_bookmark(website_title=random_sentence(including_word='tag1')),
self.setup_bookmark(website_description=random_sentence(including_word='tag1')),
]
self.term1_tag1_bookmarks = [ self.term1_tag1_bookmarks = [
self.setup_bookmark(url='http://example.com/term1', tags=[tag1]), self.setup_bookmark(url='http://example.com/term1', tags=[tag1]),
self.setup_bookmark(title=random_sentence(including_word='term1'), tags=[tag1]), self.setup_bookmark(title=random_sentence(including_word='term1'), tags=[tag1]),
@@ -110,6 +119,13 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(website_title=random_sentence(), tags=[tag1, self.setup_tag()]), self.setup_bookmark(website_title=random_sentence(), tags=[tag1, self.setup_tag()]),
self.setup_bookmark(website_description=random_sentence(), tags=[tag1, self.setup_tag()]), self.setup_bookmark(website_description=random_sentence(), tags=[tag1, self.setup_tag()]),
] ]
self.tag1_as_term_bookmarks = [
self.setup_bookmark(url='http://example.com/tag1'),
self.setup_bookmark(title=random_sentence(including_word='tag1')),
self.setup_bookmark(description=random_sentence(including_word='tag1')),
self.setup_bookmark(website_title=random_sentence(including_word='tag1')),
self.setup_bookmark(website_description=random_sentence(including_word='tag1')),
]
self.term1_tag1_bookmarks = [ self.term1_tag1_bookmarks = [
self.setup_bookmark(url='http://example.com/term1', tags=[tag1, self.setup_tag()]), self.setup_bookmark(url='http://example.com/term1', tags=[tag1, self.setup_tag()]),
self.setup_bookmark(title=random_sentence(including_word='term1'), tags=[tag1, self.setup_tag()]), self.setup_bookmark(title=random_sentence(including_word='term1'), tags=[tag1, self.setup_tag()]),
@@ -143,12 +159,13 @@ 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.get_or_create_test_user(), '') query = queries.query_bookmarks(self.user, self.profile, '')
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.other_bookmarks, self.other_bookmarks,
self.term1_bookmarks, self.term1_bookmarks,
self.term1_term2_bookmarks, self.term1_term2_bookmarks,
self.tag1_bookmarks, self.tag1_bookmarks,
self.tag1_as_term_bookmarks,
self.term1_tag1_bookmarks, self.term1_tag1_bookmarks,
self.tag2_bookmarks, self.tag2_bookmarks,
self.tag1_tag2_bookmarks self.tag1_tag2_bookmarks
@@ -157,7 +174,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.get_or_create_test_user(), 'term1') query = queries.query_bookmarks(self.user, self.profile, 'term1')
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.term1_bookmarks, self.term1_bookmarks,
self.term1_term2_bookmarks, self.term1_term2_bookmarks,
@@ -167,63 +184,101 @@ 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.get_or_create_test_user(), 'term2 term1') query = queries.query_bookmarks(self.user, self.profile, '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.get_or_create_test_user(), '#tag1') query = queries.query_bookmarks(self.user, self.profile, '#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.get_or_create_test_user(), '#tag1 #tag2') query = queries.query_bookmarks(self.user, self.profile, '#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.get_or_create_test_user(), '#Tag1 #TAG2') query = queries.query_bookmarks(self.user, self.profile, '#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.get_or_create_test_user(), 'term1 #tag1') query = queries.query_bookmarks(self.user, self.profile, 'term1 #tag1')
self.assertQueryResult(query, [self.term1_tag1_bookmarks]) self.assertQueryResult(query, [self.term1_tag1_bookmarks])
def test_query_bookmarks_in_strict_mode_should_not_search_tags_as_terms(self):
self.setup_bookmark_search_data()
self.profile.tag_search = UserProfile.TAG_SEARCH_STRICT
self.profile.save()
query = queries.query_bookmarks(self.user, self.profile, 'tag1')
self.assertQueryResult(query, [self.tag1_as_term_bookmarks])
def test_query_bookmarks_in_lax_mode_should_search_tags_as_terms(self):
self.setup_bookmark_search_data()
self.profile.tag_search = UserProfile.TAG_SEARCH_LAX
self.profile.save()
query = queries.query_bookmarks(self.user, self.profile, 'tag1')
self.assertQueryResult(query, [
self.tag1_bookmarks,
self.tag1_as_term_bookmarks,
self.tag1_tag2_bookmarks,
self.term1_tag1_bookmarks
])
query = queries.query_bookmarks(self.user, self.profile, 'tag1 term1')
self.assertQueryResult(query, [
self.term1_tag1_bookmarks,
])
query = queries.query_bookmarks(self.user, self.profile, 'tag1 tag2')
self.assertQueryResult(query, [
self.tag1_tag2_bookmarks,
])
query = queries.query_bookmarks(self.user, self.profile, 'tag1 #tag2')
self.assertQueryResult(query, [
self.tag1_tag2_bookmarks,
])
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.get_or_create_test_user(), 'term3') query = queries.query_bookmarks(self.user, self.profile, 'term3')
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
query = queries.query_bookmarks(self.get_or_create_test_user(), 'term1 term3') query = queries.query_bookmarks(self.user, self.profile, 'term1 term3')
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
query = queries.query_bookmarks(self.get_or_create_test_user(), 'term1 #tag2') query = queries.query_bookmarks(self.user, self.profile, 'term1 #tag2')
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
query = queries.query_bookmarks(self.get_or_create_test_user(), '#tag3') query = queries.query_bookmarks(self.user, self.profile, '#tag3')
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
# Unused tag # Unused tag
query = queries.query_bookmarks(self.get_or_create_test_user(), '#unused_tag1') query = queries.query_bookmarks(self.user, self.profile, '#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.get_or_create_test_user(), '#tag1 #unused_tag1') query = queries.query_bookmarks(self.user, self.profile, '#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.get_or_create_test_user(), 'term1 #unused_tag1') query = queries.query_bookmarks(self.user, self.profile, '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):
@@ -233,7 +288,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.get_or_create_test_user(), '') query = queries.query_bookmarks(self.user, self.profile, '')
self.assertQueryResult(query, [[bookmark1, bookmark2]]) self.assertQueryResult(query, [[bookmark1, bookmark2]])
@@ -244,7 +299,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark() self.setup_bookmark()
self.setup_bookmark() self.setup_bookmark()
query = queries.query_archived_bookmarks(self.get_or_create_test_user(), '') query = queries.query_archived_bookmarks(self.user, self.profile, '')
self.assertQueryResult(query, [[bookmark1, bookmark2]]) self.assertQueryResult(query, [[bookmark1, bookmark2]])
@@ -259,7 +314,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, '') query = queries.query_bookmarks(self.user, self.profile, '')
self.assertQueryResult(query, [owned_bookmarks]) self.assertQueryResult(query, [owned_bookmarks])
@@ -274,7 +329,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, '') query = queries.query_archived_bookmarks(self.user, self.profile, '')
self.assertQueryResult(query, [owned_bookmarks]) self.assertQueryResult(query, [owned_bookmarks])
@@ -284,7 +339,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, '!untagged') query = queries.query_bookmarks(self.user, self.profile, '!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):
@@ -293,7 +348,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, '!untagged term1') query = queries.query_bookmarks(self.user, self.profile, '!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):
@@ -302,7 +357,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, f'!untagged #{tag.name}') query = queries.query_bookmarks(self.user, self.profile, 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):
@@ -311,7 +366,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, '!untagged') query = queries.query_archived_bookmarks(self.user, self.profile, '!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):
@@ -320,7 +375,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, '!untagged term1') query = queries.query_archived_bookmarks(self.user, self.profile, '!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):
@@ -329,7 +384,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, f'!untagged #{tag.name}') query = queries.query_archived_bookmarks(self.user, self.profile, 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):
@@ -342,7 +397,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark() self.setup_bookmark()
self.setup_bookmark() self.setup_bookmark()
query = queries.query_bookmarks(self.user, '!unread') query = queries.query_bookmarks(self.user, self.profile, '!unread')
self.assertCountEqual(list(query), unread_bookmarks) self.assertCountEqual(list(query), unread_bookmarks)
def test_query_archived_bookmarks_unread_should_return_unread_bookmarks_only(self): def test_query_archived_bookmarks_unread_should_return_unread_bookmarks_only(self):
@@ -355,13 +410,13 @@ 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_archived_bookmarks(self.user, '!unread') query = queries.query_archived_bookmarks(self.user, self.profile, '!unread')
self.assertCountEqual(list(query), unread_bookmarks) self.assertCountEqual(list(query), unread_bookmarks)
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, '') query = queries.query_bookmark_tags(self.user, self.profile, '')
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.other_bookmarks), self.get_tags_from_bookmarks(self.other_bookmarks),
@@ -376,7 +431,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, 'term1') query = queries.query_bookmark_tags(self.user, self.profile, 'term1')
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.term1_bookmarks), self.get_tags_from_bookmarks(self.term1_bookmarks),
@@ -387,7 +442,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, 'term2 term1') query = queries.query_bookmark_tags(self.user, self.profile, '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),
@@ -396,7 +451,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, '#tag1') query = queries.query_bookmark_tags(self.user, self.profile, '#tag1')
self.assertQueryResult(query, [ self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.tag1_bookmarks), self.get_tags_from_bookmarks(self.tag1_bookmarks),
@@ -407,7 +462,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, '#tag1 #tag2') query = queries.query_bookmark_tags(self.user, self.profile, '#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),
@@ -416,7 +471,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, '#Tag1 #TAG2') query = queries.query_bookmark_tags(self.user, self.profile, '#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),
@@ -425,37 +480,75 @@ 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, 'term1 #tag1') query = queries.query_bookmark_tags(self.user, self.profile, '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),
]) ])
def test_query_bookmark_tags_in_strict_mode_should_not_search_tags_as_terms(self):
self.setup_tag_search_data()
self.profile.tag_search = UserProfile.TAG_SEARCH_STRICT
self.profile.save()
query = queries.query_bookmark_tags(self.user, self.profile, 'tag1')
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):
self.setup_tag_search_data()
self.profile.tag_search = UserProfile.TAG_SEARCH_LAX
self.profile.save()
query = queries.query_bookmark_tags(self.user, self.profile, 'tag1')
self.assertQueryResult(query, [
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_tag2_bookmarks),
self.get_tags_from_bookmarks(self.term1_tag1_bookmarks)
])
query = queries.query_bookmark_tags(self.user, self.profile, 'tag1 term1')
self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.term1_tag1_bookmarks),
])
query = queries.query_bookmark_tags(self.user, self.profile, 'tag1 tag2')
self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks),
])
query = queries.query_bookmark_tags(self.user, self.profile, 'tag1 #tag2')
self.assertQueryResult(query, [
self.get_tags_from_bookmarks(self.tag1_tag2_bookmarks),
])
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.get_or_create_test_user(), 'term3') query = queries.query_bookmark_tags(self.user, self.profile, 'term3')
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
query = queries.query_bookmark_tags(self.get_or_create_test_user(), 'term1 term3') query = queries.query_bookmark_tags(self.user, self.profile, 'term1 term3')
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
query = queries.query_bookmark_tags(self.get_or_create_test_user(), 'term1 #tag2') query = queries.query_bookmark_tags(self.user, self.profile, 'term1 #tag2')
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
query = queries.query_bookmark_tags(self.get_or_create_test_user(), '#tag3') query = queries.query_bookmark_tags(self.user, self.profile, '#tag3')
self.assertQueryResult(query, []) self.assertQueryResult(query, [])
# Unused tag # Unused tag
query = queries.query_bookmark_tags(self.get_or_create_test_user(), '#unused_tag1') query = queries.query_bookmark_tags(self.user, self.profile, '#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.get_or_create_test_user(), '#tag1 #unused_tag1') query = queries.query_bookmark_tags(self.user, self.profile, '#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.get_or_create_test_user(), 'term1 #unused_tag1') query = queries.query_bookmark_tags(self.user, self.profile, '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):
@@ -465,7 +558,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.get_or_create_test_user(), '') query = queries.query_bookmark_tags(self.user, self.profile, '')
self.assertQueryResult(query, [[tag1]]) self.assertQueryResult(query, [[tag1]])
@@ -475,7 +568,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.get_or_create_test_user(), '') query = queries.query_bookmark_tags(self.user, self.profile, '')
self.assertQueryResult(query, [[tag]]) self.assertQueryResult(query, [[tag]])
@@ -486,7 +579,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.get_or_create_test_user(), '') query = queries.query_archived_bookmark_tags(self.user, self.profile, '')
self.assertQueryResult(query, [[tag2]]) self.assertQueryResult(query, [[tag2]])
@@ -496,7 +589,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.get_or_create_test_user(), '') query = queries.query_archived_bookmark_tags(self.user, self.profile, '')
self.assertQueryResult(query, [[tag]]) self.assertQueryResult(query, [[tag]])
@@ -511,7 +604,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, '') query = queries.query_bookmark_tags(self.user, self.profile, '')
self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)]) self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)])
@@ -526,7 +619,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, '') query = queries.query_archived_bookmark_tags(self.user, self.profile, '')
self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)]) self.assertQueryResult(query, [self.get_tags_from_bookmarks(owned_bookmarks)])
@@ -537,13 +630,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, '!untagged') query = queries.query_bookmark_tags(self.user, self.profile, '!untagged')
self.assertCountEqual(list(query), []) self.assertCountEqual(list(query), [])
query = queries.query_bookmark_tags(self.user, '!untagged term1') query = queries.query_bookmark_tags(self.user, self.profile, '!untagged term1')
self.assertCountEqual(list(query), []) self.assertCountEqual(list(query), [])
query = queries.query_bookmark_tags(self.user, f'!untagged #{tag.name}') query = queries.query_bookmark_tags(self.user, self.profile, 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):
@@ -553,13 +646,13 @@ 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, '!untagged') query = queries.query_archived_bookmark_tags(self.user, self.profile, '!untagged')
self.assertCountEqual(list(query), []) self.assertCountEqual(list(query), [])
query = queries.query_archived_bookmark_tags(self.user, '!untagged term1') query = queries.query_archived_bookmark_tags(self.user, self.profile, '!untagged term1')
self.assertCountEqual(list(query), []) self.assertCountEqual(list(query), [])
query = queries.query_archived_bookmark_tags(self.user, f'!untagged #{tag.name}') query = queries.query_archived_bookmark_tags(self.user, self.profile, f'!untagged #{tag.name}')
self.assertCountEqual(list(query), []) self.assertCountEqual(list(query), [])
def test_query_shared_bookmarks(self): def test_query_shared_bookmarks(self):
@@ -582,14 +675,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, '') query_set = queries.query_shared_bookmarks(None, self.profile, '')
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, 'test title') query_set = queries.query_shared_bookmarks(None, self.profile, 'test title')
self.assertQueryResult(query_set, [[shared_bookmarks[0]]]) self.assertQueryResult(query_set, [[shared_bookmarks[0]]])
query_set = queries.query_shared_bookmarks(None, '#' + tag.name) query_set = queries.query_shared_bookmarks(None, self.profile, '#' + tag.name)
self.assertQueryResult(query_set, [[shared_bookmarks[2]]]) self.assertQueryResult(query_set, [[shared_bookmarks[2]]])
def test_query_shared_bookmark_tags(self): def test_query_shared_bookmark_tags(self):
@@ -613,7 +706,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, '') query_set = queries.query_shared_bookmark_tags(None, self.profile, '')
self.assertQueryResult(query_set, [shared_tags]) self.assertQueryResult(query_set, [shared_tags])
@@ -638,9 +731,9 @@ 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('') query_set = queries.query_shared_bookmark_users(self.profile, '')
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('test title') query_set = queries.query_shared_bookmark_users(self.profile, 'test title')
self.assertQueryResult(query_set, [[users_with_shared_bookmarks[0]]]) self.assertQueryResult(query_set, [[users_with_shared_bookmarks[0]]])

View File

@@ -28,6 +28,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
'web_archive_integration': UserProfile.WEB_ARCHIVE_INTEGRATION_DISABLED, 'web_archive_integration': UserProfile.WEB_ARCHIVE_INTEGRATION_DISABLED,
'enable_sharing': False, 'enable_sharing': False,
'enable_favicons': False, 'enable_favicons': False,
'tag_search': UserProfile.TAG_SEARCH_STRICT,
} }
return {**form_data, **overrides} return {**form_data, **overrides}
@@ -52,6 +53,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
'web_archive_integration': UserProfile.WEB_ARCHIVE_INTEGRATION_ENABLED, 'web_archive_integration': UserProfile.WEB_ARCHIVE_INTEGRATION_ENABLED,
'enable_sharing': True, 'enable_sharing': True,
'enable_favicons': True, 'enable_favicons': True,
'tag_search': UserProfile.TAG_SEARCH_LAX,
} }
response = self.client.post(reverse('bookmarks:settings.general'), form_data) response = self.client.post(reverse('bookmarks:settings.general'), form_data)
html = response.content.decode() html = response.content.decode()
@@ -65,6 +67,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertEqual(self.user.profile.web_archive_integration, form_data['web_archive_integration']) self.assertEqual(self.user.profile.web_archive_integration, form_data['web_archive_integration'])
self.assertEqual(self.user.profile.enable_sharing, form_data['enable_sharing']) self.assertEqual(self.user.profile.enable_sharing, form_data['enable_sharing'])
self.assertEqual(self.user.profile.enable_favicons, form_data['enable_favicons']) self.assertEqual(self.user.profile.enable_favicons, form_data['enable_favicons'])
self.assertEqual(self.user.profile.tag_search, form_data['tag_search'])
self.assertInHTML(''' self.assertInHTML('''
<p class="form-input-hint">Profile updated</p> <p class="form-input-hint">Profile updated</p>
''', html) ''', html)

View File

@@ -3,7 +3,7 @@ from typing import List
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 Tag from bookmarks.models import Tag, UserProfile
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
@@ -14,6 +14,7 @@ class TagCloudTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
rf = RequestFactory() rf = RequestFactory()
request = rf.get(url) request = rf.get(url)
request.user = self.get_or_create_test_user()
context = RequestContext(request, { context = RequestContext(request, {
'request': request, 'request': request,
'tags': tags, 'tags': tags,
@@ -118,6 +119,36 @@ class TagCloudTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
</a> </a>
''', rendered_template) ''', rendered_template)
def test_selected_tags_with_lax_tag_search(self):
profile = self.get_or_create_test_user().profile
profile.tag_search = UserProfile.TAG_SEARCH_LAX
profile.save()
tags = [
self.setup_tag(name='tag1'),
self.setup_tag(name='tag2'),
]
# Filter by tag name without hash
rendered_template = self.render_template(tags, tags, url='/test?q=tag1 %23tag2')
self.assertNumSelectedTags(rendered_template, 2)
# Tag name should still be removed from query string
self.assertInHTML('''
<a href="?q=%23tag2"
class="text-bold mr-2">
<span>-tag1</span>
</a>
''', rendered_template)
self.assertInHTML('''
<a href="?q=tag1"
class="text-bold mr-2">
<span>-tag2</span>
</a>
''', rendered_template)
def test_selected_tags_ignore_casing_when_removing_query_part(self): def test_selected_tags_ignore_casing_when_removing_query_part(self):
tags = [ tags = [
self.setup_tag(name='TEST'), self.setup_tag(name='TEST'),

View File

@@ -1,4 +1,5 @@
import urllib.parse import urllib.parse
from typing import List
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
@@ -9,7 +10,7 @@ from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
from bookmarks import queries from bookmarks import queries
from bookmarks.models import Bookmark, BookmarkForm, BookmarkFilters, User, Tag, build_tag_string from bookmarks.models import Bookmark, BookmarkForm, BookmarkFilters, User, UserProfile, Tag, build_tag_string
from bookmarks.services.bookmarks import create_bookmark, update_bookmark, archive_bookmark, archive_bookmarks, \ from bookmarks.services.bookmarks import create_bookmark, update_bookmark, archive_bookmark, archive_bookmarks, \
unarchive_bookmark, unarchive_bookmarks, delete_bookmarks, tag_bookmarks, untag_bookmarks unarchive_bookmark, unarchive_bookmarks, delete_bookmarks, tag_bookmarks, untag_bookmarks
from bookmarks.utils import get_safe_return_url from bookmarks.utils import get_safe_return_url
@@ -20,8 +21,8 @@ _default_page_size = 30
@login_required @login_required
def index(request): def index(request):
filters = BookmarkFilters(request) filters = BookmarkFilters(request)
query_set = queries.query_bookmarks(request.user, filters.query) query_set = queries.query_bookmarks(request.user, request.user.profile, filters.query)
tags = queries.query_bookmark_tags(request.user, filters.query) tags = queries.query_bookmark_tags(request.user, request.user.profile, filters.query)
base_url = reverse('bookmarks:index') base_url = reverse('bookmarks:index')
context = get_bookmark_view_context(request, filters, query_set, tags, base_url) context = get_bookmark_view_context(request, filters, query_set, tags, base_url)
return render(request, 'bookmarks/index.html', context) return render(request, 'bookmarks/index.html', context)
@@ -30,8 +31,8 @@ def index(request):
@login_required @login_required
def archived(request): def archived(request):
filters = BookmarkFilters(request) filters = BookmarkFilters(request)
query_set = queries.query_archived_bookmarks(request.user, filters.query) query_set = queries.query_archived_bookmarks(request.user, request.user.profile, filters.query)
tags = queries.query_archived_bookmark_tags(request.user, filters.query) tags = queries.query_archived_bookmark_tags(request.user, request.user.profile, filters.query)
base_url = reverse('bookmarks:archived') base_url = reverse('bookmarks:archived')
context = get_bookmark_view_context(request, filters, query_set, tags, base_url) context = get_bookmark_view_context(request, filters, query_set, tags, base_url)
return render(request, 'bookmarks/archive.html', context) return render(request, 'bookmarks/archive.html', context)
@@ -41,27 +42,23 @@ def archived(request):
def shared(request): def shared(request):
filters = BookmarkFilters(request) filters = BookmarkFilters(request)
user = User.objects.filter(username=filters.user).first() user = User.objects.filter(username=filters.user).first()
query_set = queries.query_shared_bookmarks(user, filters.query) query_set = queries.query_shared_bookmarks(user, request.user.profile, filters.query)
tags = queries.query_shared_bookmark_tags(user, filters.query) tags = queries.query_shared_bookmark_tags(user, request.user.profile, filters.query)
users = queries.query_shared_bookmark_users(filters.query) users = queries.query_shared_bookmark_users(request.user.profile, filters.query)
base_url = reverse('bookmarks:shared') base_url = reverse('bookmarks:shared')
context = get_bookmark_view_context(request, filters, query_set, tags, base_url) context = get_bookmark_view_context(request, filters, query_set, tags, base_url)
context['users'] = users context['users'] = users
return render(request, 'bookmarks/shared.html', context) return render(request, 'bookmarks/shared.html', context)
def _get_selected_tags(tags: QuerySet[Tag], query_string: str): def _get_selected_tags(tags: List[Tag], query_string: str, profile: UserProfile):
parsed_query = queries.parse_query_string(query_string) parsed_query = queries.parse_query_string(query_string)
tag_names = parsed_query['tag_names'] tag_names = parsed_query['tag_names']
if profile.tag_search == UserProfile.TAG_SEARCH_LAX:
tag_names = tag_names + parsed_query['search_terms']
tag_names = [tag_name.lower() for tag_name in tag_names]
if len(tag_names) == 0: return [tag for tag in tags if tag.name.lower() in tag_names]
return []
condition = Q()
for tag_name in parsed_query['tag_names']:
condition = condition | Q(name__iexact=tag_name)
return list(tags.filter(condition))
def get_bookmark_view_context(request: WSGIRequest, def get_bookmark_view_context(request: WSGIRequest,
@@ -72,7 +69,8 @@ def get_bookmark_view_context(request: WSGIRequest,
page = request.GET.get('page') page = request.GET.get('page')
paginator = Paginator(query_set, _default_page_size) paginator = Paginator(query_set, _default_page_size)
bookmarks = paginator.get_page(page) bookmarks = paginator.get_page(page)
selected_tags = _get_selected_tags(tags, filters.query) tags = list(tags)
selected_tags = _get_selected_tags(tags, filters.query, request.user.profile)
# Prefetch related objects, this avoids n+1 queries when accessing fields in templates # Prefetch related objects, this avoids n+1 queries when accessing fields in templates
prefetch_related_objects(bookmarks.object_list, 'owner', 'tags') prefetch_related_objects(bookmarks.object_list, 'owner', 'tags')
return_url = generate_return_url(base_url, page, filters) return_url = generate_return_url(base_url, page, filters)

View File

@@ -141,7 +141,7 @@ def bookmark_import(request):
def bookmark_export(request): def bookmark_export(request):
# noinspection PyBroadException # noinspection PyBroadException
try: try:
bookmarks = list(query_bookmarks(request.user, '')) bookmarks = list(query_bookmarks(request.user, request.user.profile, ''))
# Prefetch tags to prevent n+1 queries # Prefetch tags to prevent n+1 queries
prefetch_related_objects(bookmarks, 'tags') prefetch_related_objects(bookmarks, 'tags')
file_content = exporter.export_netscape_html(bookmarks) file_content = exporter.export_netscape_html(bookmarks)