linkding/bookmarks/views/bookmarks.py
Sascha Ißbrücker 41f79e35a0
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
2023-10-01 21:22:44 +02:00

271 lines
9.2 KiB
Python

import urllib.parse
from django.contrib.auth.decorators import login_required
from django.db.models import QuerySet
from django.http import HttpResponseRedirect, Http404, HttpResponseBadRequest, HttpResponseForbidden
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):
if request.method == 'POST':
return search_action(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):
if request.method == 'POST':
return search_action(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):
if request.method == 'POST':
return search_action(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, bookmark_list.search, public_only)
return render(request, 'bookmarks/shared.html', {
'bookmark_list': bookmark_list,
'tag_cloud': tag_cloud,
'users': users
})
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):
# 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.GET)
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.GET)
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')