linkding/bookmarks/views/settings.py

234 lines
8 KiB
Python
Raw Normal View History

import logging
import time
from functools import lru_cache
import requests
from django.conf import settings as django_settings
2019-07-05 08:04:52 +00:00
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.db.models import prefetch_related_objects
2019-12-26 12:45:12 +00:00
from django.http import HttpResponseRedirect, HttpResponse
2019-07-05 08:04:52 +00:00
from django.shortcuts import render
from django.urls import reverse
from rest_framework.authtoken.models import Token
2019-07-05 08:04:52 +00:00
from bookmarks.models import (
Bookmark,
UserProfileForm,
FeedToken,
GlobalSettings,
GlobalSettingsForm,
)
from bookmarks.services import exporter, tasks
2021-05-14 21:34:53 +00:00
from bookmarks.services import importer
from bookmarks.utils import app_version
2019-07-05 08:04:52 +00:00
logger = logging.getLogger(__name__)
2019-07-05 08:04:52 +00:00
@login_required
2021-03-28 10:11:56 +00:00
def general(request):
profile_form = None
global_settings_form = None
enable_refresh_favicons = django_settings.LD_ENABLE_REFRESH_FAVICONS
has_snapshot_support = django_settings.LD_ENABLE_SNAPSHOTS
success_message = _find_message_with_tag(
2024-01-27 10:29:16 +00:00
messages.get_messages(request), "bookmark_import_success"
)
error_message = _find_message_with_tag(
2024-01-27 10:29:16 +00:00
messages.get_messages(request), "bookmark_import_errors"
)
version_info = get_version_info(get_ttl_hash())
2024-01-27 10:29:16 +00:00
if request.method == "POST":
if "update_profile" in request.POST:
profile_form = update_profile(request)
success_message = "Profile updated"
if "update_global_settings" in request.POST:
global_settings_form = update_global_settings(request)
success_message = "Global settings updated"
2024-01-27 10:29:16 +00:00
if "refresh_favicons" in request.POST:
tasks.schedule_refresh_favicons(request.user)
success_message = "Scheduled favicon update. This may take a while..."
if "create_missing_html_snapshots" in request.POST:
count = tasks.create_missing_html_snapshots(request.user)
if count > 0:
success_message = (
f"Queued {count} missing snapshots. This may take a while..."
)
else:
success_message = "No missing snapshots found."
if not profile_form:
profile_form = UserProfileForm(instance=request.user_profile)
if request.user.is_superuser and not global_settings_form:
global_settings_form = GlobalSettingsForm(instance=GlobalSettings.get())
2024-01-27 10:29:16 +00:00
return render(
request,
"settings/general.html",
{
"form": profile_form,
"global_settings_form": global_settings_form,
2024-01-27 10:29:16 +00:00
"enable_refresh_favicons": enable_refresh_favicons,
"has_snapshot_support": has_snapshot_support,
"success_message": success_message,
"error_message": error_message,
2024-01-27 10:29:16 +00:00
"version_info": version_info,
},
)
def update_profile(request):
user = request.user
profile = user.profile
favicons_were_enabled = profile.enable_favicons
previews_were_enabled = profile.enable_preview_images
form = UserProfileForm(request.POST, instance=profile)
if form.is_valid():
form.save()
# Load missing favicons if the feature was just enabled
if profile.enable_favicons and not favicons_were_enabled:
tasks.schedule_bookmarks_without_favicons(request.user)
# Load missing preview images if the feature was just enabled
if profile.enable_preview_images and not previews_were_enabled:
tasks.schedule_bookmarks_without_previews(request.user)
return form
def update_global_settings(request):
user = request.user
if not user.is_superuser:
raise PermissionDenied()
form = GlobalSettingsForm(request.POST, instance=GlobalSettings.get())
if form.is_valid():
form.save()
return form
# Cache API call response, for one hour when using get_ttl_hash with default params
@lru_cache(maxsize=1)
def get_version_info(ttl_hash=None):
latest_version = None
try:
2024-01-27 10:29:16 +00:00
latest_version_url = (
"https://api.github.com/repos/sissbruecker/linkding/releases/latest"
)
response = requests.get(latest_version_url, timeout=5)
json = response.json()
2024-01-27 10:29:16 +00:00
if response.status_code == 200 and "name" in json:
latest_version = json["name"][1:]
except requests.exceptions.RequestException:
pass
2024-01-27 10:29:16 +00:00
latest_version_info = ""
if latest_version == app_version:
2024-01-27 10:29:16 +00:00
latest_version_info = " (latest)"
elif latest_version is not None:
2024-01-27 10:29:16 +00:00
latest_version_info = f" (latest: {latest_version})"
2024-01-27 10:29:16 +00:00
return f"{app_version}{latest_version_info}"
def get_ttl_hash(seconds=3600):
"""Return the same value within `seconds` time period"""
return round(time.time() / seconds)
@login_required
def integrations(request):
2024-01-27 10:29:16 +00:00
application_url = request.build_absolute_uri(reverse("bookmarks:new"))
api_token = Token.objects.get_or_create(user=request.user)[0]
feed_token = FeedToken.objects.get_or_create(user=request.user)[0]
2024-01-27 10:29:16 +00:00
all_feed_url = request.build_absolute_uri(
reverse("bookmarks:feeds.all", args=[feed_token.key])
)
unread_feed_url = request.build_absolute_uri(
reverse("bookmarks:feeds.unread", args=[feed_token.key])
)
shared_feed_url = request.build_absolute_uri(
reverse("bookmarks:feeds.shared", args=[feed_token.key])
)
public_shared_feed_url = request.build_absolute_uri(
reverse("bookmarks:feeds.public_shared")
)
2024-01-27 10:29:16 +00:00
return render(
request,
"settings/integrations.html",
{
"application_url": application_url,
"api_token": api_token.key,
"all_feed_url": all_feed_url,
"unread_feed_url": unread_feed_url,
"shared_feed_url": shared_feed_url,
"public_shared_feed_url": public_shared_feed_url,
2024-01-27 10:29:16 +00:00
},
)
2019-07-05 08:04:52 +00:00
@login_required
2019-07-06 06:14:13 +00:00
def bookmark_import(request):
2024-01-27 10:29:16 +00:00
import_file = request.FILES.get("import_file")
import_options = importer.ImportOptions(
map_private_flag=request.POST.get("map_private_flag") == "on"
)
if import_file is None:
2024-01-27 10:29:16 +00:00
messages.error(
request, "Please select a file to import.", "bookmark_import_errors"
)
return HttpResponseRedirect(reverse("bookmarks:settings.general"))
2019-07-05 08:04:52 +00:00
try:
content = import_file.read().decode()
result = importer.import_netscape_html(content, request.user, import_options)
2024-01-27 10:29:16 +00:00
success_msg = str(result.success) + " bookmarks were successfully imported."
messages.success(request, success_msg, "bookmark_import_success")
if result.failed > 0:
2024-01-27 10:29:16 +00:00
err_msg = (
str(result.failed)
+ " bookmarks could not be imported. Please check the logs for more details."
)
messages.error(request, err_msg, "bookmark_import_errors")
except:
2024-01-27 10:29:16 +00:00
logging.exception("Unexpected error during bookmark import")
messages.error(
request,
"An error occurred during bookmark import.",
"bookmark_import_errors",
)
2019-07-05 08:04:52 +00:00
pass
2024-01-27 10:29:16 +00:00
return HttpResponseRedirect(reverse("bookmarks:settings.general"))
2019-07-05 08:04:52 +00:00
2019-12-26 12:45:12 +00:00
@login_required
def bookmark_export(request):
# noinspection PyBroadException
2019-12-26 12:45:12 +00:00
try:
bookmarks = Bookmark.objects.filter(owner=request.user)
# Prefetch tags to prevent n+1 queries
2024-01-27 10:29:16 +00:00
prefetch_related_objects(bookmarks, "tags")
2021-05-14 21:34:53 +00:00
file_content = exporter.export_netscape_html(bookmarks)
2019-12-26 12:45:12 +00:00
2024-01-27 10:29:16 +00:00
response = HttpResponse(content_type="text/plain; charset=UTF-8")
response["Content-Disposition"] = 'attachment; filename="bookmarks.html"'
2019-12-26 12:45:12 +00:00
response.write(file_content)
return response
except:
2024-01-27 10:29:16 +00:00
return render(
request,
"settings/general.html",
{"export_error": "An error occurred during bookmark export."},
)
2019-12-26 12:45:12 +00:00
2019-07-05 08:04:52 +00:00
def _find_message_with_tag(messages, tag):
for message in messages:
if message.extra_tags == tag:
return message