diff --git a/bookmarks/services/exporter.py b/bookmarks/services/exporter.py
index 1bdff35d..dc0717fb 100644
--- a/bookmarks/services/exporter.py
+++ b/bookmarks/services/exporter.py
@@ -33,7 +33,10 @@ def append_bookmark(doc: BookmarkDocument, bookmark: Bookmark):
desc = html.escape(bookmark.resolved_description or '')
if bookmark.notes:
desc += f'[linkding-notes]{html.escape(bookmark.notes)}[/linkding-notes]'
- tags = ','.join(bookmark.tag_names)
+ tag_names = bookmark.tag_names
+ if bookmark.is_archived:
+ tag_names.append('linkding:archived')
+ tags = ','.join(tag_names)
toread = '1' if bookmark.unread else '0'
private = '0' if bookmark.shared else '1'
added = int(bookmark.date_added.timestamp())
diff --git a/bookmarks/services/importer.py b/bookmarks/services/importer.py
index abffd824..96f8fd0e 100644
--- a/bookmarks/services/importer.py
+++ b/bookmarks/services/importer.py
@@ -5,7 +5,7 @@
from django.contrib.auth.models import User
from django.utils import timezone
-from bookmarks.models import Bookmark, Tag, parse_tag_string
+from bookmarks.models import Bookmark, Tag
from bookmarks.services import tasks
from bookmarks.services.parser import parse, NetscapeBookmark
from bookmarks.utils import parse_timestamp
@@ -93,8 +93,7 @@ def _create_missing_tags(netscape_bookmarks: List[NetscapeBookmark], user: User)
tags_to_create = []
for netscape_bookmark in netscape_bookmarks:
- tag_names = parse_tag_string(netscape_bookmark.tag_string)
- for tag_name in tag_names:
+ for tag_name in netscape_bookmark.tag_names:
tag = tag_cache.get(tag_name)
if not tag:
tag = Tag(name=tag_name, owner=user)
@@ -194,8 +193,7 @@ def _import_batch(netscape_bookmarks: List[NetscapeBookmark],
continue
# Get tag models by string, schedule inserts for bookmark -> tag associations
- tag_names = parse_tag_string(netscape_bookmark.tag_string)
- tags = tag_cache.get_all(tag_names)
+ tags = tag_cache.get_all(netscape_bookmark.tag_names)
for tag in tags:
relationships.append(BookmarkToTagRelationShip(bookmark=bookmark, tag=tag))
@@ -219,3 +217,5 @@ def _copy_bookmark_data(netscape_bookmark: NetscapeBookmark, bookmark: Bookmark,
bookmark.notes = netscape_bookmark.notes
if options.map_private_flag and not netscape_bookmark.private:
bookmark.shared = True
+ if netscape_bookmark.archived:
+ bookmark.is_archived = True
diff --git a/bookmarks/services/parser.py b/bookmarks/services/parser.py
index 61bafc47..0004dbc5 100644
--- a/bookmarks/services/parser.py
+++ b/bookmarks/services/parser.py
@@ -2,6 +2,8 @@
from html.parser import HTMLParser
from typing import Dict, List
+from bookmarks.models import parse_tag_string
+
@dataclass
class NetscapeBookmark:
@@ -10,9 +12,10 @@ class NetscapeBookmark:
description: str
notes: str
date_added: str
- tag_string: str
+ tag_names: List[str]
to_read: bool
private: bool
+ archived: bool
class BookmarkParser(HTMLParser):
@@ -56,16 +59,24 @@ def handle_start_dt(self, attrs: Dict[str, str]):
def handle_start_a(self, attrs: Dict[str, str]):
vars(self).update(attrs)
+ tag_names = parse_tag_string(self.tags)
+ archived = 'linkding:archived' in self.tags
+ try:
+ tag_names.remove('linkding:archived')
+ except ValueError:
+ pass
+
self.bookmark = NetscapeBookmark(
href=self.href,
title='',
description='',
notes='',
date_added=self.add_date,
- tag_string=self.tags,
+ tag_names=tag_names,
to_read=self.toread == '1',
# Mark as private by default, also when attribute is not specified
private=self.private != '0',
+ archived=archived,
)
def handle_a_data(self, data):
diff --git a/bookmarks/tests/test_exporter.py b/bookmarks/tests/test_exporter.py
index 8e6b7aad..033246c8 100644
--- a/bookmarks/tests/test_exporter.py
+++ b/bookmarks/tests/test_exporter.py
@@ -22,6 +22,9 @@ def test_export_bookmarks(self):
description='Example description', notes='Example notes'),
self.setup_bookmark(url='https://example.com/6', title='Title 6', added=added, shared=True,
notes='Example notes'),
+ self.setup_bookmark(url='https://example.com/7', title='Title 7', added=added, is_archived=True),
+ self.setup_bookmark(url='https://example.com/8', title='Title 8', added=added,
+ tags=[self.setup_tag(name='tag4'), self.setup_tag(name='tag5')], is_archived=True),
]
html = exporter.export_netscape_html(bookmarks)
@@ -35,6 +38,8 @@ def test_export_bookmarks(self):
'
Example description[linkding-notes]Example notes[/linkding-notes]',
f'Title 6',
'[linkding-notes]Example notes[/linkding-notes]',
+ f'Title 7',
+ f'Title 8',
]
self.assertIn('\n\r'.join(lines), html)
diff --git a/bookmarks/tests/test_importer.py b/bookmarks/tests/test_importer.py
index 9c2675c4..9fc0d5da 100644
--- a/bookmarks/tests/test_importer.py
+++ b/bookmarks/tests/test_importer.py
@@ -295,6 +295,27 @@ def test_private_flag(self):
self.assertEqual(bookmark2.shared, False)
self.assertEqual(bookmark3.shared, True)
+ def test_archived_state(self):
+ test_html = self.render_html(tags_html='''
+ Example title 1
+ Example description 1
+ Example title 2
+ Example description 2
+ Example title 3
+ Example description 3
+ ''')
+ import_netscape_html(test_html, self.get_or_create_test_user(), ImportOptions())
+
+ self.assertEqual(Bookmark.objects.count(), 3)
+ self.assertEqual(Bookmark.objects.all()[0].is_archived, True)
+ self.assertEqual(Bookmark.objects.all()[1].is_archived, False)
+ self.assertEqual(Bookmark.objects.all()[2].is_archived, False)
+
+ tags = Tag.objects.all()
+ self.assertEqual(len(tags), 2)
+ self.assertEqual(tags[0].name, 'tag1')
+ self.assertEqual(tags[1].name, 'tag2')
+
def test_notes(self):
# initial notes
test_html = self.render_html(tags_html='''
diff --git a/bookmarks/tests/test_parser.py b/bookmarks/tests/test_parser.py
index 1b2403b2..7d8ddc67 100644
--- a/bookmarks/tests/test_parser.py
+++ b/bookmarks/tests/test_parser.py
@@ -2,6 +2,7 @@
from django.test import TestCase
+from bookmarks.models import parse_tag_string
from bookmarks.services.parser import NetscapeBookmark
from bookmarks.services.parser import parse
from bookmarks.tests.helpers import ImportTestMixin, BookmarkHtmlTag
@@ -16,7 +17,7 @@ def assertTagsEqual(self, bookmarks: List[NetscapeBookmark], html_tags: List[Boo
self.assertEqual(bookmark.title, html_tag.title)
self.assertEqual(bookmark.date_added, html_tag.add_date)
self.assertEqual(bookmark.description, html_tag.description)
- self.assertEqual(bookmark.tag_string, html_tag.tags)
+ self.assertEqual(bookmark.tag_names, parse_tag_string(html_tag.tags))
self.assertEqual(bookmark.to_read, html_tag.to_read)
self.assertEqual(bookmark.private, html_tag.private)
diff --git a/bookmarks/tests/test_settings_export_view.py b/bookmarks/tests/test_settings_export_view.py
index e1d56b18..8f72e3d8 100644
--- a/bookmarks/tests/test_settings_export_view.py
+++ b/bookmarks/tests/test_settings_export_view.py
@@ -3,6 +3,7 @@
from django.test import TestCase
from django.urls import reverse
+from bookmarks.models import Bookmark
from bookmarks.tests.helpers import BookmarkFactoryMixin
@@ -20,6 +21,9 @@ def test_should_export_successfully(self):
self.setup_bookmark(tags=[self.setup_tag()])
self.setup_bookmark(tags=[self.setup_tag()])
self.setup_bookmark(tags=[self.setup_tag()])
+ self.setup_bookmark(tags=[self.setup_tag()], is_archived=True)
+ self.setup_bookmark(tags=[self.setup_tag()], is_archived=True)
+ self.setup_bookmark(tags=[self.setup_tag()], is_archived=True)
response = self.client.get(
reverse('bookmarks:settings.export'),
@@ -30,6 +34,35 @@ def test_should_export_successfully(self):
self.assertEqual(response['content-type'], 'text/plain; charset=UTF-8')
self.assertEqual(response['Content-Disposition'], 'attachment; filename="bookmarks.html"')
+ for bookmark in Bookmark.objects.all():
+ self.assertContains(response, bookmark.url)
+
+ def test_should_only_export_user_bookmarks(self):
+ other_user = self.setup_user()
+ owned_bookmarks = [
+ self.setup_bookmark(tags=[self.setup_tag()]),
+ self.setup_bookmark(tags=[self.setup_tag()]),
+ self.setup_bookmark(tags=[self.setup_tag()]),
+ ]
+ non_owned_bookmarks = [
+ self.setup_bookmark(tags=[self.setup_tag()], user=other_user),
+ self.setup_bookmark(tags=[self.setup_tag()], user=other_user),
+ self.setup_bookmark(tags=[self.setup_tag()], user=other_user),
+ ]
+
+ response = self.client.get(
+ reverse('bookmarks:settings.export'),
+ follow=True
+ )
+
+ text = response.content.decode('utf-8')
+
+ for bookmark in owned_bookmarks:
+ self.assertIn(bookmark.url, text)
+
+ for bookmark in non_owned_bookmarks:
+ self.assertNotIn(bookmark.url, text)
+
def test_should_check_authentication(self):
self.client.logout()
response = self.client.get(reverse('bookmarks:settings.export'), follow=True)
diff --git a/bookmarks/views/settings.py b/bookmarks/views/settings.py
index d174805f..768a8c30 100644
--- a/bookmarks/views/settings.py
+++ b/bookmarks/views/settings.py
@@ -12,8 +12,7 @@
from django.urls import reverse
from rest_framework.authtoken.models import Token
-from bookmarks.models import BookmarkSearch, UserProfileForm, FeedToken
-from bookmarks.queries import query_bookmarks
+from bookmarks.models import Bookmark, BookmarkSearch, UserProfileForm, FeedToken
from bookmarks.services import exporter, tasks
from bookmarks.services import importer
from bookmarks.utils import app_version
@@ -136,7 +135,7 @@ def bookmark_import(request):
def bookmark_export(request):
# noinspection PyBroadException
try:
- bookmarks = list(query_bookmarks(request.user, request.user_profile, BookmarkSearch()))
+ bookmarks = Bookmark.objects.filter(owner=request.user)
# Prefetch tags to prevent n+1 queries
prefetch_related_objects(bookmarks, 'tags')
file_content = exporter.export_netscape_html(bookmarks)