Add sort option to bookmark list (#522)

* Rename BookmarkFilters to BookmarkSearch

* Refactor queries to accept BookmarkSearch

* Sort query by data added and title

* Ensure pagination respects search parameters

* Ensure tag cloud respects search parameters

* Ensure user select respects search parameters

* Ensure return url respects search options

* Fix passing search options to user select

* Fix BookmarkSearch initialization

* Extract common search form logic

* Ensure partial update respects search options

* Add sort UI

* Use custom ICU collation when sorting with SQLite

* Support sort in API
This commit is contained in:
Sascha Ißbrücker
2023-09-01 22:48:21 +02:00
committed by GitHub
parent 0c50906056
commit 0975914a86
35 changed files with 1026 additions and 361 deletions

View File

@@ -7,8 +7,8 @@ from django.db import models
from django.urls import reverse
from bookmarks import queries
from bookmarks.models import Bookmark, BookmarkFilters, User, UserProfile, Tag
from bookmarks import utils
from bookmarks.models import Bookmark, BookmarkSearch, BookmarkSearchForm, User, UserProfile, Tag
DEFAULT_PAGE_SIZE = 30
@@ -55,7 +55,7 @@ class BookmarkItem:
class BookmarkListContext:
def __init__(self, request: WSGIRequest) -> None:
self.request = request
self.filters = BookmarkFilters(self.request)
self.search = BookmarkSearch.from_request(self.request)
user = request.user
user_profile = request.user_profile
@@ -71,29 +71,37 @@ class BookmarkListContext:
self.is_empty = paginator.count == 0
self.bookmarks_page = bookmarks_page
self.bookmarks_total = paginator.count
self.return_url = self.generate_return_url(page_number)
self.return_url = self.generate_return_url(self.search, self.get_base_url(), page_number)
self.action_url = self.generate_action_url(self.search, self.get_base_action_url(), self.return_url)
self.link_target = user_profile.bookmark_link_target
self.date_display = user_profile.bookmark_date_display
self.show_url = user_profile.display_url
self.show_favicons = user_profile.enable_favicons
self.show_notes = user_profile.permanent_notes
def generate_return_url(self, page: int):
base_url = self.get_base_url()
url_query = {}
if self.filters.query:
url_query['q'] = self.filters.query
if self.filters.user:
url_query['user'] = self.filters.user
@staticmethod
def generate_return_url(search: BookmarkSearch, base_url: str, page: int = None):
query_params = search.query_params
if page is not None:
url_query['page'] = page
url_params = urllib.parse.urlencode(url_query)
return_url = base_url if url_params == '' else base_url + '?' + url_params
return urllib.parse.quote_plus(return_url)
query_params['page'] = page
query_string = urllib.parse.urlencode(query_params)
return base_url if query_string == '' else base_url + '?' + query_string
@staticmethod
def generate_action_url(search: BookmarkSearch, base_action_url: str, return_url: str):
query_params = search.query_params
query_params['return_url'] = return_url
query_string = urllib.parse.urlencode(query_params)
return base_action_url if query_string == '' else base_action_url + '?' + query_string
def get_base_url(self):
raise Exception(f'Must be implemented by subclass')
def get_base_action_url(self):
raise Exception(f'Must be implemented by subclass')
def get_bookmark_query_set(self):
raise Exception(f'Must be implemented by subclass')
@@ -102,32 +110,41 @@ class ActiveBookmarkListContext(BookmarkListContext):
def get_base_url(self):
return reverse('bookmarks:index')
def get_base_action_url(self):
return reverse('bookmarks:index.action')
def get_bookmark_query_set(self):
return queries.query_bookmarks(self.request.user,
self.request.user_profile,
self.filters.query)
self.search)
class ArchivedBookmarkListContext(BookmarkListContext):
def get_base_url(self):
return reverse('bookmarks:archived')
def get_base_action_url(self):
return reverse('bookmarks:archived.action')
def get_bookmark_query_set(self):
return queries.query_archived_bookmarks(self.request.user,
self.request.user_profile,
self.filters.query)
self.search)
class SharedBookmarkListContext(BookmarkListContext):
def get_base_url(self):
return reverse('bookmarks:shared')
def get_base_action_url(self):
return reverse('bookmarks:shared.action')
def get_bookmark_query_set(self):
user = User.objects.filter(username=self.filters.user).first()
user = User.objects.filter(username=self.search.user).first()
public_only = not self.request.user.is_authenticated
return queries.query_shared_bookmarks(user,
self.request.user_profile,
self.filters.query,
self.search,
public_only)
@@ -159,7 +176,7 @@ class TagGroup:
class TagCloudContext:
def __init__(self, request: WSGIRequest) -> None:
self.request = request
self.filters = BookmarkFilters(self.request)
self.search = BookmarkSearch.from_request(self.request)
query_set = self.get_tag_query_set()
tags = list(query_set)
@@ -179,7 +196,7 @@ class TagCloudContext:
raise Exception(f'Must be implemented by subclass')
def get_selected_tags(self, tags: List[Tag]):
parsed_query = queries.parse_query_string(self.filters.query)
parsed_query = queries.parse_query_string(self.search.query)
tag_names = parsed_query['tag_names']
if self.request.user_profile.tag_search == UserProfile.TAG_SEARCH_LAX:
tag_names = tag_names + parsed_query['search_terms']
@@ -192,21 +209,21 @@ class ActiveTagCloudContext(TagCloudContext):
def get_tag_query_set(self):
return queries.query_bookmark_tags(self.request.user,
self.request.user_profile,
self.filters.query)
self.search)
class ArchivedTagCloudContext(TagCloudContext):
def get_tag_query_set(self):
return queries.query_archived_bookmark_tags(self.request.user,
self.request.user_profile,
self.filters.query)
self.search)
class SharedTagCloudContext(TagCloudContext):
def get_tag_query_set(self):
user = User.objects.filter(username=self.filters.user).first()
user = User.objects.filter(username=self.search.user).first()
public_only = not self.request.user.is_authenticated
return queries.query_shared_bookmark_tags(user,
self.request.user_profile,
self.filters.query,
self.search,
public_only)