Skip to content

Commit

Permalink
Add LAST_MODIFIED attribute when exporting (#860)
Browse files Browse the repository at this point in the history
* add LAST_MODIFIED attribute when exporting

* complement test_exporter for LAST_MODIFIED attribute

* parse LAST_MODIFIED attribute when importing

* use bookmark date_added when no modified date is parsed, otherwise use parsed datetime.

* complement test_parser and test_importer for LAST_MODIFIED attribute

* cleanup tests a bit

---------

Co-authored-by: Sascha Ißbrücker <[email protected]>
  • Loading branch information
ixzhao and sissbruecker authored Oct 2, 2024
1 parent 0dd05b9 commit 1f2cf21
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 28 deletions.
3 changes: 2 additions & 1 deletion bookmarks/services/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ def append_bookmark(doc: BookmarkDocument, bookmark: Bookmark):
toread = "1" if bookmark.unread else "0"
private = "0" if bookmark.shared else "1"
added = int(bookmark.date_added.timestamp())
modified = int(bookmark.date_modified.timestamp())

doc.append(
f'<DT><A HREF="{url}" ADD_DATE="{added}" PRIVATE="{private}" TOREAD="{toread}" TAGS="{tags}">{title}</A>'
f'<DT><A HREF="{url}" ADD_DATE="{added}" LAST_MODIFIED="{modified}" PRIVATE="{private}" TOREAD="{toread}" TAGS="{tags}">{title}</A>'
)

if desc:
Expand Down
5 changes: 4 additions & 1 deletion bookmarks/services/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,10 @@ def _copy_bookmark_data(
bookmark.date_added = parse_timestamp(netscape_bookmark.date_added)
else:
bookmark.date_added = timezone.now()
bookmark.date_modified = bookmark.date_added
if netscape_bookmark.date_modified:
bookmark.date_modified = parse_timestamp(netscape_bookmark.date_modified)
else:
bookmark.date_modified = bookmark.date_added
bookmark.unread = netscape_bookmark.to_read
if netscape_bookmark.title:
bookmark.title = netscape_bookmark.title
Expand Down
4 changes: 4 additions & 0 deletions bookmarks/services/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class NetscapeBookmark:
description: str
notes: str
date_added: str
date_modified: str
tag_names: List[str]
to_read: bool
private: bool
Expand All @@ -27,6 +28,7 @@ def __init__(self):
self.bookmark = None
self.href = ""
self.add_date = ""
self.last_modified = ""
self.tags = ""
self.title = ""
self.description = ""
Expand Down Expand Up @@ -72,6 +74,7 @@ def handle_start_a(self, attrs: Dict[str, str]):
description="",
notes="",
date_added=self.add_date,
date_modified=self.last_modified,
tag_names=tag_names,
to_read=self.toread == "1",
# Mark as private by default, also when attribute is not specified
Expand All @@ -97,6 +100,7 @@ def add_bookmark(self):
self.bookmark = None
self.href = ""
self.add_date = ""
self.last_modified = ""
self.tags = ""
self.title = ""
self.description = ""
Expand Down
8 changes: 7 additions & 1 deletion bookmarks/tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def setup_bookmark(
favicon_file: str = "",
preview_image_file: str = "",
added: datetime = None,
modified: datetime = None,
):
if title is None:
title = get_random_string(length=32)
Expand All @@ -57,13 +58,15 @@ def setup_bookmark(
url = "https://example.com/" + unique_id
if added is None:
added = timezone.now()
if modified is None:
modified = timezone.now()
bookmark = Bookmark(
url=url,
title=title,
description=description,
notes=notes,
date_added=added,
date_modified=timezone.now(),
date_modified=modified,
owner=user,
is_archived=is_archived,
unread=unread,
Expand Down Expand Up @@ -320,6 +323,7 @@ def __init__(
title: str = "",
description: str = "",
add_date: str = "",
last_modified: str = "",
tags: str = "",
to_read: bool = False,
private: bool = True,
Expand All @@ -328,6 +332,7 @@ def __init__(
self.title = title
self.description = description
self.add_date = add_date
self.last_modified = last_modified
self.tags = tags
self.to_read = to_read
self.private = private
Expand All @@ -339,6 +344,7 @@ def render_tag(self, tag: BookmarkHtmlTag):
<DT>
<A {f'HREF="{tag.href}"' if tag.href else ''}
{f'ADD_DATE="{tag.add_date}"' if tag.add_date else ''}
{f'LAST_MODIFIED="{tag.last_modified}"' if tag.last_modified else ''}
{f'TAGS="{tag.tags}"' if tag.tags else ''}
TOREAD="{1 if tag.to_read else 0}"
PRIVATE="{1 if tag.private else 0}">
Expand Down
52 changes: 32 additions & 20 deletions bookmarks/tests/test_exporter.py
Original file line number Diff line number Diff line change
@@ -1,81 +1,93 @@
from datetime import datetime, timezone

from django.test import TestCase
from django.utils import timezone

from bookmarks.services import exporter
from bookmarks.tests.helpers import BookmarkFactoryMixin


class ExporterTestCase(TestCase, BookmarkFactoryMixin):
def test_export_bookmarks(self):
added = timezone.now()
timestamp = int(added.timestamp())

bookmarks = [
self.setup_bookmark(
url="https://example.com/1",
title="Title 1",
added=added,
added=datetime.fromtimestamp(1, timezone.utc),
modified=datetime.fromtimestamp(11, timezone.utc),
description="Example description",
),
self.setup_bookmark(
url="https://example.com/2",
title="Title 2",
added=added,
added=datetime.fromtimestamp(2, timezone.utc),
modified=datetime.fromtimestamp(22, timezone.utc),
tags=[
self.setup_tag(name="tag1"),
self.setup_tag(name="tag2"),
self.setup_tag(name="tag3"),
],
),
self.setup_bookmark(
url="https://example.com/3", title="Title 3", added=added, unread=True
url="https://example.com/3",
title="Title 3",
added=datetime.fromtimestamp(3, timezone.utc),
modified=datetime.fromtimestamp(33, timezone.utc),
unread=True,
),
self.setup_bookmark(
url="https://example.com/4", title="Title 4", added=added, shared=True
url="https://example.com/4",
title="Title 4",
added=datetime.fromtimestamp(4, timezone.utc),
modified=datetime.fromtimestamp(44, timezone.utc),
shared=True,
),
self.setup_bookmark(
url="https://example.com/5",
title="Title 5",
added=added,
added=datetime.fromtimestamp(5, timezone.utc),
modified=datetime.fromtimestamp(55, timezone.utc),
shared=True,
description="Example description",
notes="Example notes",
),
self.setup_bookmark(
url="https://example.com/6",
title="Title 6",
added=added,
added=datetime.fromtimestamp(6, timezone.utc),
modified=datetime.fromtimestamp(66, timezone.utc),
shared=True,
notes="Example notes",
),
self.setup_bookmark(
url="https://example.com/7",
title="Title 7",
added=added,
added=datetime.fromtimestamp(7, timezone.utc),
modified=datetime.fromtimestamp(77, timezone.utc),
is_archived=True,
),
self.setup_bookmark(
url="https://example.com/8",
title="Title 8",
added=added,
added=datetime.fromtimestamp(8, timezone.utc),
modified=datetime.fromtimestamp(88, timezone.utc),
tags=[self.setup_tag(name="tag4"), self.setup_tag(name="tag5")],
is_archived=True,
),
]
html = exporter.export_netscape_html(bookmarks)

lines = [
f'<DT><A HREF="https://example.com/1" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="0" TAGS="">Title 1</A>',
'<DT><A HREF="https://example.com/1" ADD_DATE="1" LAST_MODIFIED="11" PRIVATE="1" TOREAD="0" TAGS="">Title 1</A>',
"<DD>Example description",
f'<DT><A HREF="https://example.com/2" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="0" TAGS="tag1,tag2,tag3">Title 2</A>',
f'<DT><A HREF="https://example.com/3" ADD_DATE="{timestamp}" PRIVATE="1" TOREAD="1" TAGS="">Title 3</A>',
f'<DT><A HREF="https://example.com/4" ADD_DATE="{timestamp}" PRIVATE="0" TOREAD="0" TAGS="">Title 4</A>',
f'<DT><A HREF="https://example.com/5" ADD_DATE="{timestamp}" PRIVATE="0" TOREAD="0" TAGS="">Title 5</A>',
'<DT><A HREF="https://example.com/2" ADD_DATE="2" LAST_MODIFIED="22" PRIVATE="1" TOREAD="0" TAGS="tag1,tag2,tag3">Title 2</A>',
'<DT><A HREF="https://example.com/3" ADD_DATE="3" LAST_MODIFIED="33" PRIVATE="1" TOREAD="1" TAGS="">Title 3</A>',
'<DT><A HREF="https://example.com/4" ADD_DATE="4" LAST_MODIFIED="44" PRIVATE="0" TOREAD="0" TAGS="">Title 4</A>',
'<DT><A HREF="https://example.com/5" ADD_DATE="5" LAST_MODIFIED="55" PRIVATE="0" TOREAD="0" TAGS="">Title 5</A>',
"<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>',
'<DT><A HREF="https://example.com/6" ADD_DATE="6" LAST_MODIFIED="66" 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>',
'<DT><A HREF="https://example.com/7" ADD_DATE="7" LAST_MODIFIED="77" PRIVATE="1" TOREAD="0" TAGS="linkding:archived">Title 7</A>',
'<DT><A HREF="https://example.com/8" ADD_DATE="8" LAST_MODIFIED="88" PRIVATE="1" TOREAD="0" TAGS="tag4,tag5,linkding:archived">Title 8</A>',
]
self.assertIn("\n\r".join(lines), html)

Expand Down
41 changes: 38 additions & 3 deletions bookmarks/tests/test_importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ def assertBookmarksImported(self, html_tags: List[BookmarkHtmlTag]):
self.assertEqual(bookmark.title, html_tag.title)
self.assertEqual(bookmark.description, html_tag.description)
self.assertEqual(bookmark.date_added, parse_timestamp(html_tag.add_date))
self.assertEqual(
bookmark.date_modified, parse_timestamp(html_tag.last_modified)
)
self.assertEqual(bookmark.unread, html_tag.to_read)
self.assertEqual(bookmark.shared, not html_tag.private)

Expand All @@ -45,27 +48,31 @@ def test_import(self):
title="Example title",
description="Example description",
add_date="1",
last_modified="11",
tags="example-tag",
),
BookmarkHtmlTag(
href="https://example.com/foo",
title="Foo title",
description="",
add_date="2",
last_modified="22",
tags="",
),
BookmarkHtmlTag(
href="https://example.com/bar",
title="Bar title",
description="Bar description",
add_date="3",
last_modified="33",
tags="bar-tag, other-tag",
),
BookmarkHtmlTag(
href="https://example.com/baz",
title="Baz title",
description="Baz description",
add_date="4",
last_modified="44",
to_read=True,
),
]
Expand All @@ -90,34 +97,39 @@ def test_synchronize(self):
title="Example title",
description="Example description",
add_date="1",
last_modified="11",
tags="example-tag",
),
BookmarkHtmlTag(
href="https://example.com/foo",
title="Foo title",
description="",
add_date="2",
last_modified="22",
tags="",
),
BookmarkHtmlTag(
href="https://example.com/bar",
title="Bar title",
description="Bar description",
add_date="3",
last_modified="33",
tags="bar-tag, other-tag",
),
BookmarkHtmlTag(
href="https://example.com/unread",
title="Unread title",
description="Unread description",
add_date="3",
add_date="4",
last_modified="44",
to_read=True,
),
BookmarkHtmlTag(
href="https://example.com/private",
title="Private title",
description="Private description",
add_date="4",
add_date="5",
last_modified="55",
private=True,
),
]
Expand All @@ -136,37 +148,47 @@ def test_synchronize(self):
title="Updated Example title",
description="Updated Example description",
add_date="111",
last_modified="1111",
tags="updated-example-tag",
),
BookmarkHtmlTag(
href="https://example.com/foo",
title="Updated Foo title",
description="Updated Foo description",
add_date="222",
last_modified="2222",
tags="new-tag",
),
BookmarkHtmlTag(
href="https://example.com/bar",
title="Updated Bar title",
description="Updated Bar description",
add_date="333",
last_modified="3333",
tags="updated-bar-tag, updated-other-tag",
),
BookmarkHtmlTag(
href="https://example.com/unread",
title="Unread title",
description="Unread description",
add_date="3",
last_modified="3",
to_read=False,
),
BookmarkHtmlTag(
href="https://example.com/private",
title="Private title",
description="Private description",
add_date="4",
last_modified="4",
private=False,
),
BookmarkHtmlTag(href="https://baz.com", add_date="444", tags="baz-tag"),
BookmarkHtmlTag(
href="https://baz.com",
add_date="444",
last_modified="4444",
tags="baz-tag",
),
]

# Import updated data
Expand Down Expand Up @@ -291,6 +313,19 @@ def test_use_current_date_when_no_add_date(self):
Bookmark.objects.all()[0].date_added, timezone.datetime(2021, 1, 1)
)

def test_use_add_date_when_no_last_modified(self):
test_html = self.render_html(
tags_html=f"""
<DT><A HREF="https://example.com" ADD_DATE="1">Example.com</A>
<DD>Example.com
"""
)

import_netscape_html(test_html, self.get_or_create_test_user())

self.assertEqual(Bookmark.objects.count(), 1)
self.assertEqual(Bookmark.objects.all()[0].date_modified, parse_timestamp("1"))

def test_keep_title_if_imported_bookmark_has_empty_title(self):
test_html = self.render_html(
tags=[BookmarkHtmlTag(href="https://example.com", title="Example.com")]
Expand Down
Loading

0 comments on commit 1f2cf21

Please sign in to comment.