2022-08-04 17:37:16 +00:00
|
|
|
from typing import Optional
|
|
|
|
|
2023-09-01 20:48:21 +00:00
|
|
|
from django.conf import settings
|
2019-06-29 10:53:37 +00:00
|
|
|
from django.contrib.auth.models import User
|
2023-09-01 20:48:21 +00:00
|
|
|
from django.db.models import Q, QuerySet, Exists, OuterRef, Case, When, CharField
|
|
|
|
from django.db.models.expressions import RawSQL
|
|
|
|
from django.db.models.functions import Lower
|
2019-06-29 10:53:37 +00:00
|
|
|
|
2023-09-01 20:48:21 +00:00
|
|
|
from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile
|
2021-01-02 10:30:20 +00:00
|
|
|
from bookmarks.utils import unique
|
2019-06-29 10:53:37 +00:00
|
|
|
|
|
|
|
|
2023-09-01 20:48:21 +00:00
|
|
|
def query_bookmarks(user: User, profile: UserProfile, search: BookmarkSearch) -> QuerySet:
|
|
|
|
return _base_bookmarks_query(user, profile, search) \
|
2021-02-14 17:00:22 +00:00
|
|
|
.filter(is_archived=False)
|
|
|
|
|
|
|
|
|
2023-09-01 20:48:21 +00:00
|
|
|
def query_archived_bookmarks(user: User, profile: UserProfile, search: BookmarkSearch) -> QuerySet:
|
|
|
|
return _base_bookmarks_query(user, profile, search) \
|
2021-02-14 17:00:22 +00:00
|
|
|
.filter(is_archived=True)
|
|
|
|
|
|
|
|
|
2023-09-01 20:48:21 +00:00
|
|
|
def query_shared_bookmarks(user: Optional[User], profile: UserProfile, search: BookmarkSearch,
|
2023-08-14 22:20:52 +00:00
|
|
|
public_only: bool) -> QuerySet:
|
|
|
|
conditions = Q(shared=True) & Q(owner__profile__enable_sharing=True)
|
|
|
|
if public_only:
|
|
|
|
conditions = conditions & Q(owner__profile__enable_public_sharing=True)
|
|
|
|
|
2023-09-01 20:48:21 +00:00
|
|
|
return _base_bookmarks_query(user, profile, search).filter(conditions)
|
2022-08-04 17:37:16 +00:00
|
|
|
|
|
|
|
|
2023-09-01 20:48:21 +00:00
|
|
|
def _base_bookmarks_query(user: Optional[User], profile: UserProfile, search: BookmarkSearch) -> QuerySet:
|
2022-09-09 17:46:55 +00:00
|
|
|
query_set = Bookmark.objects
|
2019-06-29 10:53:37 +00:00
|
|
|
|
|
|
|
# Filter for user
|
2022-08-04 17:37:16 +00:00
|
|
|
if user:
|
|
|
|
query_set = query_set.filter(owner=user)
|
2019-06-29 10:53:37 +00:00
|
|
|
|
2019-06-30 17:54:33 +00:00
|
|
|
# Split query into search terms and tags
|
2023-09-01 20:48:21 +00:00
|
|
|
query = parse_query_string(search.query)
|
2019-06-30 17:54:33 +00:00
|
|
|
|
|
|
|
# Filter for search terms and tags
|
2019-06-30 19:15:02 +00:00
|
|
|
for term in query['search_terms']:
|
2023-05-18 07:06:22 +00:00
|
|
|
conditions = Q(title__icontains=term) \
|
|
|
|
| Q(description__icontains=term) \
|
2023-05-20 09:54:26 +00:00
|
|
|
| Q(notes__icontains=term) \
|
2023-05-18 07:06:22 +00:00
|
|
|
| Q(website_title__icontains=term) \
|
|
|
|
| Q(website_description__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)
|
2019-06-30 17:54:33 +00:00
|
|
|
|
2019-06-30 19:15:02 +00:00
|
|
|
for tag_name in query['tag_names']:
|
2019-06-29 10:53:37 +00:00
|
|
|
query_set = query_set.filter(
|
2021-01-02 10:30:20 +00:00
|
|
|
tags__name__iexact=tag_name
|
2019-06-29 10:53:37 +00:00
|
|
|
)
|
|
|
|
|
2022-05-21 07:50:51 +00:00
|
|
|
# Untagged bookmarks
|
|
|
|
if query['untagged']:
|
|
|
|
query_set = query_set.filter(
|
|
|
|
tags=None
|
|
|
|
)
|
2023-09-16 08:39:27 +00:00
|
|
|
# Legacy unread bookmarks filter from query
|
2022-07-23 20:17:20 +00:00
|
|
|
if query['unread']:
|
|
|
|
query_set = query_set.filter(
|
|
|
|
unread=True
|
|
|
|
)
|
2022-05-21 07:50:51 +00:00
|
|
|
|
2023-09-16 08:39:27 +00:00
|
|
|
# Unread filter from bookmark search
|
|
|
|
if search.unread == BookmarkSearch.FILTER_UNREAD_YES:
|
|
|
|
query_set = query_set.filter(unread=True)
|
|
|
|
elif search.unread == BookmarkSearch.FILTER_UNREAD_NO:
|
|
|
|
query_set = query_set.filter(unread=False)
|
|
|
|
|
2023-09-10 20:14:07 +00:00
|
|
|
# Shared filter
|
|
|
|
if search.shared == BookmarkSearch.FILTER_SHARED_SHARED:
|
|
|
|
query_set = query_set.filter(shared=True)
|
|
|
|
elif search.shared == BookmarkSearch.FILTER_SHARED_UNSHARED:
|
|
|
|
query_set = query_set.filter(shared=False)
|
|
|
|
|
2021-03-31 07:08:19 +00:00
|
|
|
# Sort by date added
|
2023-09-01 20:48:21 +00:00
|
|
|
if search.sort == BookmarkSearch.SORT_ADDED_ASC:
|
|
|
|
query_set = query_set.order_by('date_added')
|
|
|
|
elif search.sort == BookmarkSearch.SORT_ADDED_DESC:
|
|
|
|
query_set = query_set.order_by('-date_added')
|
|
|
|
|
|
|
|
# Sort by title
|
|
|
|
if search.sort == BookmarkSearch.SORT_TITLE_ASC or search.sort == BookmarkSearch.SORT_TITLE_DESC:
|
|
|
|
# For the title, the resolved_title logic from the Bookmark entity needs
|
|
|
|
# to be replicated as there is no corresponding database field
|
|
|
|
query_set = query_set.annotate(
|
|
|
|
effective_title=Case(
|
|
|
|
When(Q(title__isnull=False) & ~Q(title__exact=''), then=Lower('title')),
|
|
|
|
When(Q(website_title__isnull=False) & ~Q(website_title__exact=''), then=Lower('website_title')),
|
|
|
|
default=Lower('url'),
|
|
|
|
output_field=CharField()
|
|
|
|
))
|
|
|
|
|
|
|
|
# For SQLite, if the ICU extension is loaded, use the custom collation
|
|
|
|
# loaded into the connection. This results in an improved sort order for
|
|
|
|
# unicode characters (umlauts, etc.)
|
|
|
|
if settings.USE_SQLITE and settings.USE_SQLITE_ICU_EXTENSION:
|
|
|
|
order_field = RawSQL('effective_title COLLATE ICU', ())
|
|
|
|
else:
|
|
|
|
order_field = 'effective_title'
|
|
|
|
|
|
|
|
if search.sort == BookmarkSearch.SORT_TITLE_ASC:
|
|
|
|
query_set = query_set.order_by(order_field)
|
|
|
|
elif search.sort == BookmarkSearch.SORT_TITLE_DESC:
|
|
|
|
query_set = query_set.order_by(order_field).reverse()
|
2019-06-29 10:53:37 +00:00
|
|
|
|
|
|
|
return query_set
|
2019-06-30 19:15:02 +00:00
|
|
|
|
|
|
|
|
2023-09-01 20:48:21 +00:00
|
|
|
def query_bookmark_tags(user: User, profile: UserProfile, search: BookmarkSearch) -> QuerySet:
|
|
|
|
bookmarks_query = query_bookmarks(user, profile, search)
|
2021-02-14 17:00:22 +00:00
|
|
|
|
2021-08-15 07:28:40 +00:00
|
|
|
query_set = Tag.objects.filter(bookmark__in=bookmarks_query)
|
2021-02-14 17:00:22 +00:00
|
|
|
|
2021-08-15 07:28:40 +00:00
|
|
|
return query_set.distinct()
|
2019-12-26 22:51:52 +00:00
|
|
|
|
2019-06-30 19:15:02 +00:00
|
|
|
|
2023-09-01 20:48:21 +00:00
|
|
|
def query_archived_bookmark_tags(user: User, profile: UserProfile, search: BookmarkSearch) -> QuerySet:
|
|
|
|
bookmarks_query = query_archived_bookmarks(user, profile, search)
|
2019-06-30 19:15:02 +00:00
|
|
|
|
2021-08-15 07:28:40 +00:00
|
|
|
query_set = Tag.objects.filter(bookmark__in=bookmarks_query)
|
2019-06-30 19:15:02 +00:00
|
|
|
|
|
|
|
return query_set.distinct()
|
|
|
|
|
|
|
|
|
2023-09-01 20:48:21 +00:00
|
|
|
def query_shared_bookmark_tags(user: Optional[User], profile: UserProfile, search: BookmarkSearch,
|
2023-08-14 22:20:52 +00:00
|
|
|
public_only: bool) -> QuerySet:
|
2023-09-01 20:48:21 +00:00
|
|
|
bookmarks_query = query_shared_bookmarks(user, profile, search, public_only)
|
2022-08-04 17:37:16 +00:00
|
|
|
|
|
|
|
query_set = Tag.objects.filter(bookmark__in=bookmarks_query)
|
|
|
|
|
|
|
|
return query_set.distinct()
|
|
|
|
|
|
|
|
|
2023-09-01 20:48:21 +00:00
|
|
|
def query_shared_bookmark_users(profile: UserProfile, search: BookmarkSearch, public_only: bool) -> QuerySet:
|
|
|
|
bookmarks_query = query_shared_bookmarks(None, profile, search, public_only)
|
2022-08-04 17:37:16 +00:00
|
|
|
|
|
|
|
query_set = User.objects.filter(bookmark__in=bookmarks_query)
|
|
|
|
|
|
|
|
return query_set.distinct()
|
|
|
|
|
|
|
|
|
2019-12-27 11:32:44 +00:00
|
|
|
def get_user_tags(user: User):
|
|
|
|
return Tag.objects.filter(owner=user).all()
|
|
|
|
|
|
|
|
|
2022-08-04 18:31:24 +00:00
|
|
|
def parse_query_string(query_string):
|
2019-06-30 19:15:02 +00:00
|
|
|
# Sanitize query params
|
|
|
|
if not query_string:
|
|
|
|
query_string = ''
|
|
|
|
|
|
|
|
# Split query into search terms and tags
|
|
|
|
keywords = query_string.strip().split(' ')
|
|
|
|
keywords = [word for word in keywords if word]
|
|
|
|
|
2022-05-21 07:50:51 +00:00
|
|
|
search_terms = [word for word in keywords if word[0] != '#' and word[0] != '!']
|
2019-06-30 19:15:02 +00:00
|
|
|
tag_names = [word[1:] for word in keywords if word[0] == '#']
|
2021-01-02 10:30:20 +00:00
|
|
|
tag_names = unique(tag_names, str.lower)
|
2019-06-30 19:15:02 +00:00
|
|
|
|
2022-05-21 07:50:51 +00:00
|
|
|
# Special search commands
|
|
|
|
untagged = '!untagged' in keywords
|
2022-07-23 20:17:20 +00:00
|
|
|
unread = '!unread' in keywords
|
2022-05-21 07:50:51 +00:00
|
|
|
|
2019-06-30 19:15:02 +00:00
|
|
|
return {
|
|
|
|
'search_terms': search_terms,
|
|
|
|
'tag_names': tag_names,
|
2022-05-21 07:50:51 +00:00
|
|
|
'untagged': untagged,
|
2022-07-23 20:17:20 +00:00
|
|
|
'unread': unread,
|
2019-06-30 19:15:02 +00:00
|
|
|
}
|