mirror of
https://github.com/sissbruecker/linkding
synced 2024-11-22 03:13:02 +00:00
Include archived bookmarks in export (#579)
This commit is contained in:
parent
47e944e6c5
commit
a9512b2333
8 changed files with 85 additions and 12 deletions
|
@ -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())
|
||||
|
|
|
@ -5,7 +5,7 @@ from typing import List
|
|||
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
|
||||
|
|
|
@ -2,6 +2,8 @@ from dataclasses import dataclass
|
|||
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 @@ class BookmarkParser(HTMLParser):
|
|||
|
||||
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):
|
||||
|
|
|
@ -22,6 +22,9 @@ class ExporterTestCase(TestCase, BookmarkFactoryMixin):
|
|||
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 @@ class ExporterTestCase(TestCase, BookmarkFactoryMixin):
|
|||
'<DD>Example description[linkding-notes]Example notes[/linkding-notes]',
|
||||
f'<DT><A HREF="https://example.com/6" ADD_DATE="{timestamp}" PRIVATE="0" TOREAD="0" TAGS="">Title 6</A>',
|
||||
'<DD>[linkding-notes]Example notes[/linkding-notes]',
|
||||
f'<DT><A HREF="https://example.com/7" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="0" TAGS="linkding:archived">Title 7</A>',
|
||||
f'<DT><A HREF="https://example.com/8" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="0" TAGS="tag4,tag5,linkding:archived">Title 8</A>',
|
||||
]
|
||||
self.assertIn('\n\r'.join(lines), html)
|
||||
|
||||
|
|
|
@ -295,6 +295,27 @@ class ImporterTestCase(TestCase, BookmarkFactoryMixin, ImportTestMixin):
|
|||
self.assertEqual(bookmark2.shared, False)
|
||||
self.assertEqual(bookmark3.shared, True)
|
||||
|
||||
def test_archived_state(self):
|
||||
test_html = self.render_html(tags_html='''
|
||||
<DT><A HREF="https://example.com/1" ADD_DATE="1" TAGS="tag1,tag2,linkding:archived">Example title 1</A>
|
||||
<DD>Example description 1</DD>
|
||||
<DT><A HREF="https://example.com/2" ADD_DATE="1" PRIVATE="1" TAGS="tag1,tag2">Example title 2</A>
|
||||
<DD>Example description 2</DD>
|
||||
<DT><A HREF="https://example.com/3" ADD_DATE="1" PRIVATE="0">Example title 3</A>
|
||||
<DD>Example description 3</DD>
|
||||
''')
|
||||
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='''
|
||||
|
|
|
@ -2,6 +2,7 @@ from typing import List
|
|||
|
||||
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 @@ class ParserTestCase(TestCase, ImportTestMixin):
|
|||
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)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ from unittest.mock import patch
|
|||
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 @@ class SettingsExportViewTestCase(TestCase, BookmarkFactoryMixin):
|
|||
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 @@ class SettingsExportViewTestCase(TestCase, BookmarkFactoryMixin):
|
|||
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)
|
||||
|
|
|
@ -12,8 +12,7 @@ from django.shortcuts import render
|
|||
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)
|
||||
|
|
Loading…
Reference in a new issue