diff --git a/bookmarks/services/bookmarks.py b/bookmarks/services/bookmarks.py
index f2b8a81..82b1fb2 100644
--- a/bookmarks/services/bookmarks.py
+++ b/bookmarks/services/bookmarks.py
@@ -208,6 +208,15 @@ def refresh_bookmarks_metadata(bookmark_ids: [Union[int, str]], current_user: Us
tasks.load_preview_image(current_user, bookmark)
+def create_html_snapshots(bookmark_ids: list[Union[int, str]], current_user: User):
+ sanitized_bookmark_ids = _sanitize_id_list(bookmark_ids)
+ owned_bookmarks = Bookmark.objects.filter(
+ owner=current_user, id__in=sanitized_bookmark_ids
+ )
+
+ tasks.create_html_snapshots(owned_bookmarks)
+
+
def _merge_bookmark_data(from_bookmark: Bookmark, to_bookmark: Bookmark):
to_bookmark.title = from_bookmark.title
to_bookmark.description = from_bookmark.description
diff --git a/bookmarks/templates/bookmarks/bulk_edit/bar.html b/bookmarks/templates/bookmarks/bulk_edit/bar.html
index 3648483..4f0f372 100644
--- a/bookmarks/templates/bookmarks/bulk_edit/bar.html
+++ b/bookmarks/templates/bookmarks/bulk_edit/bar.html
@@ -23,6 +23,9 @@
{% endif %}
+ {% if bookmark_list.snapshot_feature_enabled %}
+
+ {% endif %}
diff --git a/bookmarks/tests/test_bookmark_archived_view.py b/bookmarks/tests/test_bookmark_archived_view.py
index 00b9c7b..771c7fa 100644
--- a/bookmarks/tests/test_bookmark_archived_view.py
+++ b/bookmarks/tests/test_bookmark_archived_view.py
@@ -1,7 +1,7 @@
import urllib.parse
from django.contrib.auth.models import User
-from django.test import TestCase
+from django.test import TestCase, override_settings
from django.urls import reverse
from bookmarks.models import BookmarkSearch, UserProfile
@@ -319,6 +319,28 @@ class BookmarkArchivedViewTestCase(
html,
)
+ @override_settings(LD_ENABLE_SNAPSHOTS=True)
+ def test_allowed_bulk_actions_with_html_snapshot_enabled(self):
+ url = reverse("linkding:bookmarks.archived")
+ response = self.client.get(url)
+ html = response.content.decode()
+
+ self.assertInHTML(
+ f"""
+
+ """,
+ html,
+ )
+
def test_allowed_bulk_actions_with_sharing_enabled(self):
user_profile = self.user.profile
user_profile.enable_sharing = True
@@ -345,6 +367,34 @@ class BookmarkArchivedViewTestCase(
html,
)
+ @override_settings(LD_ENABLE_SNAPSHOTS=True)
+ def test_allowed_bulk_actions_with_sharing_and_html_snapshot_enabled(self):
+ user_profile = self.user.profile
+ user_profile.enable_sharing = True
+ user_profile.save()
+
+ url = reverse("linkding:bookmarks.archived")
+ response = self.client.get(url)
+ html = response.content.decode()
+
+ self.assertInHTML(
+ f"""
+
+ """,
+ html,
+ )
+
def test_apply_search_preferences(self):
# no params
response = self.client.post(reverse("linkding:bookmarks.archived"))
diff --git a/bookmarks/tests/test_bookmark_index_view.py b/bookmarks/tests/test_bookmark_index_view.py
index a82d014..b44155e 100644
--- a/bookmarks/tests/test_bookmark_index_view.py
+++ b/bookmarks/tests/test_bookmark_index_view.py
@@ -1,7 +1,7 @@
import urllib.parse
from django.contrib.auth.models import User
-from django.test import TestCase
+from django.test import TestCase, override_settings
from django.urls import reverse
from bookmarks.models import BookmarkSearch, UserProfile
@@ -313,6 +313,28 @@ class BookmarkIndexViewTestCase(
html,
)
+ @override_settings(LD_ENABLE_SNAPSHOTS=True)
+ def test_allowed_bulk_actions_with_html_snapshot_enabled(self):
+ url = reverse("linkding:bookmarks.index")
+ response = self.client.get(url)
+ html = response.content.decode()
+
+ self.assertInHTML(
+ f"""
+
+ """,
+ html,
+ )
+
def test_allowed_bulk_actions_with_sharing_enabled(self):
user_profile = self.user.profile
user_profile.enable_sharing = True
@@ -339,6 +361,34 @@ class BookmarkIndexViewTestCase(
html,
)
+ @override_settings(LD_ENABLE_SNAPSHOTS=True)
+ def test_allowed_bulk_actions_with_sharing_and_html_snapshot_enabled(self):
+ user_profile = self.user.profile
+ user_profile.enable_sharing = True
+ user_profile.save()
+
+ url = reverse("linkding:bookmarks.index")
+ response = self.client.get(url)
+ html = response.content.decode()
+
+ self.assertInHTML(
+ f"""
+
+ """,
+ html,
+ )
+
def test_apply_search_preferences(self):
# no params
response = self.client.post(reverse("linkding:bookmarks.index"))
diff --git a/bookmarks/tests/test_bookmarks_service.py b/bookmarks/tests/test_bookmarks_service.py
index db2c34a..42cb78d 100644
--- a/bookmarks/tests/test_bookmarks_service.py
+++ b/bookmarks/tests/test_bookmarks_service.py
@@ -22,6 +22,7 @@ from bookmarks.services.bookmarks import (
unshare_bookmarks,
enhance_with_website_metadata,
refresh_bookmarks_metadata,
+ create_html_snapshots,
)
from bookmarks.tests.helpers import BookmarkFactoryMixin
@@ -974,3 +975,73 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
self.assertEqual(self.mock_schedule_refresh_metadata.call_count, 3)
self.assertEqual(self.mock_load_preview_image.call_count, 3)
+
+ def test_create_html_snapshots(self):
+ with patch.object(tasks, "create_html_snapshots") as mock_create_html_snapshots:
+ bookmark1 = self.setup_bookmark()
+ bookmark2 = self.setup_bookmark()
+ bookmark3 = self.setup_bookmark()
+
+ create_html_snapshots(
+ [bookmark1.id, bookmark2.id, bookmark3.id],
+ self.get_or_create_test_user(),
+ )
+
+ mock_create_html_snapshots.assert_called_once()
+ call_args = mock_create_html_snapshots.call_args[0][0]
+ bookmark_ids = list(call_args.values_list("id", flat=True))
+ self.assertCountEqual(
+ bookmark_ids, [bookmark1.id, bookmark2.id, bookmark3.id]
+ )
+
+ def test_create_html_snapshots_should_only_create_for_specified_bookmarks(self):
+ with patch.object(tasks, "create_html_snapshots") as mock_create_html_snapshots:
+ bookmark1 = self.setup_bookmark()
+ bookmark2 = self.setup_bookmark()
+ bookmark3 = self.setup_bookmark()
+
+ create_html_snapshots(
+ [bookmark1.id, bookmark3.id], self.get_or_create_test_user()
+ )
+
+ mock_create_html_snapshots.assert_called_once()
+ call_args = mock_create_html_snapshots.call_args[0][0]
+ bookmark_ids = list(call_args.values_list("id", flat=True))
+ self.assertCountEqual(bookmark_ids, [bookmark1.id, bookmark3.id])
+ self.assertNotIn(bookmark2.id, bookmark_ids)
+
+ def test_create_html_snapshots_should_only_create_for_user_owned_bookmarks(self):
+ with patch.object(tasks, "create_html_snapshots") as mock_create_html_snapshots:
+ other_user = self.setup_user()
+ bookmark1 = self.setup_bookmark()
+ bookmark2 = self.setup_bookmark()
+ inaccessible_bookmark = self.setup_bookmark(user=other_user)
+
+ create_html_snapshots(
+ [bookmark1.id, bookmark2.id, inaccessible_bookmark.id],
+ self.get_or_create_test_user(),
+ )
+
+ mock_create_html_snapshots.assert_called_once()
+ call_args = mock_create_html_snapshots.call_args[0][0]
+ bookmark_ids = list(call_args.values_list("id", flat=True))
+ self.assertCountEqual(bookmark_ids, [bookmark1.id, bookmark2.id])
+ self.assertNotIn(inaccessible_bookmark.id, bookmark_ids)
+
+ def test_create_html_snapshots_should_accept_mix_of_int_and_string_ids(self):
+ with patch.object(tasks, "create_html_snapshots") as mock_create_html_snapshots:
+ bookmark1 = self.setup_bookmark()
+ bookmark2 = self.setup_bookmark()
+ bookmark3 = self.setup_bookmark()
+
+ create_html_snapshots(
+ [str(bookmark1.id), bookmark2.id, str(bookmark3.id)],
+ self.get_or_create_test_user(),
+ )
+
+ mock_create_html_snapshots.assert_called_once()
+ call_args = mock_create_html_snapshots.call_args[0][0]
+ bookmark_ids = list(call_args.values_list("id", flat=True))
+ self.assertCountEqual(
+ bookmark_ids, [bookmark1.id, bookmark2.id, bookmark3.id]
+ )
diff --git a/bookmarks/views/bookmarks.py b/bookmarks/views/bookmarks.py
index 7b4556c..6ed3932 100644
--- a/bookmarks/views/bookmarks.py
+++ b/bookmarks/views/bookmarks.py
@@ -31,6 +31,7 @@ from bookmarks.services.bookmarks import (
share_bookmarks,
unshare_bookmarks,
refresh_bookmarks_metadata,
+ create_html_snapshots,
)
from bookmarks.type_defs import HttpRequest
from bookmarks.utils import get_safe_return_url
@@ -368,6 +369,8 @@ def handle_action(request: HttpRequest, query: QuerySet[Bookmark] = None):
return unshare_bookmarks(bookmark_ids, request.user)
if "bulk_refresh" == bulk_action:
return refresh_bookmarks_metadata(bookmark_ids, request.user)
+ if "bulk_snapshot" == bulk_action:
+ return create_html_snapshots(bookmark_ids, request.user)
@login_required
diff --git a/bookmarks/views/contexts.py b/bookmarks/views/contexts.py
index f0e6cd4..065b1fe 100644
--- a/bookmarks/views/contexts.py
+++ b/bookmarks/views/contexts.py
@@ -219,6 +219,7 @@ class BookmarkListContext:
self.show_notes = user_profile.permanent_notes
self.collapse_side_panel = user_profile.collapse_side_panel
self.is_preview = False
+ self.snapshot_feature_enabled = settings.LD_ENABLE_SNAPSHOTS
@staticmethod
def generate_return_url(search: BookmarkSearch, base_url: str, page: int = None):