Files
linkding/bookmarks/views/bookmarks.py
Sascha Ißbrücker 0975914a86 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
2023-09-01 22:48:21 +02:00

245 lines
8.3 KiB
Python

from django.contrib.auth.decorators import login_required
from django.db.models import QuerySet
from django.http import HttpResponseRedirect, Http404, HttpResponseBadRequest
from django.shortcuts import render
from django.urls import reverse
from bookmarks import queries
from bookmarks.models import Bookmark, BookmarkForm, BookmarkSearch, build_tag_string
from bookmarks.services.bookmarks import create_bookmark, update_bookmark, archive_bookmark, archive_bookmarks, \
unarchive_bookmark, unarchive_bookmarks, delete_bookmarks, tag_bookmarks, untag_bookmarks, mark_bookmarks_as_read, \
mark_bookmarks_as_unread, share_bookmarks, unshare_bookmarks
from bookmarks.utils import get_safe_return_url
from bookmarks.views.partials import contexts
_default_page_size = 30
@login_required
def index(request):
bookmark_list = contexts.ActiveBookmarkListContext(request)
tag_cloud = contexts.ActiveTagCloudContext(request)
return render(request, 'bookmarks/index.html', {
'bookmark_list': bookmark_list,
'tag_cloud': tag_cloud,
})
@login_required
def archived(request):
bookmark_list = contexts.ArchivedBookmarkListContext(request)
tag_cloud = contexts.ArchivedTagCloudContext(request)
return render(request, 'bookmarks/archive.html', {
'bookmark_list': bookmark_list,
'tag_cloud': tag_cloud,
})
def shared(request):
search = BookmarkSearch.from_request(request)
bookmark_list = contexts.SharedBookmarkListContext(request)
tag_cloud = contexts.SharedTagCloudContext(request)
public_only = not request.user.is_authenticated
users = queries.query_shared_bookmark_users(request.user_profile, search, public_only)
return render(request, 'bookmarks/shared.html', {
'bookmark_list': bookmark_list,
'tag_cloud': tag_cloud,
'users': users
})
def convert_tag_string(tag_string: str):
# Tag strings coming from inputs are space-separated, however services.bookmarks functions expect comma-separated
# strings
return tag_string.replace(' ', ',')
@login_required
def new(request):
initial_url = request.GET.get('url')
initial_title = request.GET.get('title')
initial_description = request.GET.get('description')
initial_auto_close = 'auto_close' in request.GET
if request.method == 'POST':
form = BookmarkForm(request.POST)
auto_close = form.data['auto_close']
if form.is_valid():
current_user = request.user
tag_string = convert_tag_string(form.data['tag_string'])
create_bookmark(form.save(commit=False), tag_string, current_user)
if auto_close:
return HttpResponseRedirect(reverse('bookmarks:close'))
else:
return HttpResponseRedirect(reverse('bookmarks:index'))
else:
form = BookmarkForm()
if initial_url:
form.initial['url'] = initial_url
if initial_title:
form.initial['title'] = initial_title
if initial_description:
form.initial['description'] = initial_description
if initial_auto_close:
form.initial['auto_close'] = 'true'
context = {
'form': form,
'auto_close': initial_auto_close,
'return_url': reverse('bookmarks:index')
}
return render(request, 'bookmarks/new.html', context)
@login_required
def edit(request, bookmark_id: int):
try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist:
raise Http404('Bookmark does not exist')
return_url = get_safe_return_url(request.GET.get('return_url'), reverse('bookmarks:index'))
if request.method == 'POST':
form = BookmarkForm(request.POST, instance=bookmark)
if form.is_valid():
tag_string = convert_tag_string(form.data['tag_string'])
update_bookmark(form.save(commit=False), tag_string, request.user)
return HttpResponseRedirect(return_url)
else:
form = BookmarkForm(instance=bookmark)
form.initial['tag_string'] = build_tag_string(bookmark.tag_names, ' ')
context = {
'form': form,
'bookmark_id': bookmark_id,
'return_url': return_url
}
return render(request, 'bookmarks/edit.html', context)
def remove(request, bookmark_id: int):
try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist:
raise Http404('Bookmark does not exist')
bookmark.delete()
def archive(request, bookmark_id: int):
try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist:
raise Http404('Bookmark does not exist')
archive_bookmark(bookmark)
def unarchive(request, bookmark_id: int):
try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist:
raise Http404('Bookmark does not exist')
unarchive_bookmark(bookmark)
def unshare(request, bookmark_id: int):
try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist:
raise Http404('Bookmark does not exist')
bookmark.shared = False
bookmark.save()
def mark_as_read(request, bookmark_id: int):
try:
bookmark = Bookmark.objects.get(pk=bookmark_id, owner=request.user)
except Bookmark.DoesNotExist:
raise Http404('Bookmark does not exist')
bookmark.unread = False
bookmark.save()
@login_required
def index_action(request):
search = BookmarkSearch.from_request(request)
query = queries.query_bookmarks(request.user, request.user_profile, search)
return action(request, query)
@login_required
def archived_action(request):
search = BookmarkSearch.from_request(request)
query = queries.query_archived_bookmarks(request.user, request.user_profile, search)
return action(request, query)
@login_required
def shared_action(request):
return action(request)
def action(request, query: QuerySet[Bookmark] = None):
# Single bookmark actions
if 'archive' in request.POST:
archive(request, request.POST['archive'])
if 'unarchive' in request.POST:
unarchive(request, request.POST['unarchive'])
if 'remove' in request.POST:
remove(request, request.POST['remove'])
if 'mark_as_read' in request.POST:
mark_as_read(request, request.POST['mark_as_read'])
if 'unshare' in request.POST:
unshare(request, request.POST['unshare'])
# Bulk actions
if 'bulk_execute' in request.POST:
if query is None:
return HttpResponseBadRequest('View does not support bulk actions')
bulk_action = request.POST['bulk_action']
# Determine set of bookmarks
if request.POST.get('bulk_select_across') == 'on':
# Query full list of bookmarks across all pages
bookmark_ids = query.only('id').values_list('id', flat=True)
else:
# Use only selected bookmarks
bookmark_ids = request.POST.getlist('bookmark_id')
if 'bulk_archive' == bulk_action:
archive_bookmarks(bookmark_ids, request.user)
if 'bulk_unarchive' == bulk_action:
unarchive_bookmarks(bookmark_ids, request.user)
if 'bulk_delete' == bulk_action:
delete_bookmarks(bookmark_ids, request.user)
if 'bulk_tag' == bulk_action:
tag_string = convert_tag_string(request.POST['bulk_tag_string'])
tag_bookmarks(bookmark_ids, tag_string, request.user)
if 'bulk_untag' == bulk_action:
tag_string = convert_tag_string(request.POST['bulk_tag_string'])
untag_bookmarks(bookmark_ids, tag_string, request.user)
if 'bulk_read' == bulk_action:
mark_bookmarks_as_read(bookmark_ids, request.user)
if 'bulk_unread' == bulk_action:
mark_bookmarks_as_unread(bookmark_ids, request.user)
if 'bulk_share' == bulk_action:
share_bookmarks(bookmark_ids, request.user)
if 'bulk_unshare' == bulk_action:
unshare_bookmarks(bookmark_ids, request.user)
return_url = get_safe_return_url(request.GET.get('return_url'), reverse('bookmarks:index'))
return HttpResponseRedirect(return_url)
@login_required
def close(request):
return render(request, 'bookmarks/close.html')