Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve performance of the export bookmark service. #915

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 20 additions & 24 deletions bookmarks/services/exporter.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
import html
from typing import List
import itertools
from typing import Iterable

from bookmarks.models import Bookmark

BookmarkDocument = List[str]

def export_netscape_html(bookmarks: Iterable[Bookmark]):
def _append_bookmarks():
for bookmark in bookmarks:
yield from append_bookmark(bookmark)

def export_netscape_html(bookmarks: List[Bookmark]):
doc = []
append_header(doc)
append_list_start(doc)
[append_bookmark(doc, bookmark) for bookmark in bookmarks]
append_list_end(doc)
return itertools.chain(append_header(), append_list_start(), _append_bookmarks(), append_list_end())

return "\n\r".join(doc)

def append_header():
yield "<!DOCTYPE NETSCAPE-Bookmark-file-1>"
yield '<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">'
yield "<TITLE>Bookmarks</TITLE>"
yield "<H1>Bookmarks</H1>"

def append_header(doc: BookmarkDocument):
doc.append("<!DOCTYPE NETSCAPE-Bookmark-file-1>")
doc.append('<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">')
doc.append("<TITLE>Bookmarks</TITLE>")
doc.append("<H1>Bookmarks</H1>")

def append_list_start():
yield "<DL><p>"

def append_list_start(doc: BookmarkDocument):
doc.append("<DL><p>")


def append_bookmark(doc: BookmarkDocument, bookmark: Bookmark):
def append_bookmark(bookmark: Bookmark):
url = bookmark.url
title = html.escape(bookmark.resolved_title or "")
desc = html.escape(bookmark.resolved_description or "")
Expand All @@ -42,13 +39,12 @@ def append_bookmark(doc: BookmarkDocument, bookmark: Bookmark):
added = int(bookmark.date_added.timestamp())
modified = int(bookmark.date_modified.timestamp())

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


if desc:
doc.append(f"<DD>{desc}")
yield f"<DD>{desc}"


def append_list_end(doc: BookmarkDocument):
doc.append("</DL><p>")
def append_list_end():
yield "</DL><p>"
8 changes: 4 additions & 4 deletions bookmarks/tests/test_exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def test_export_bookmarks(self):
is_archived=True,
),
]
html = exporter.export_netscape_html(bookmarks)
html = "\r\n".join(exporter.export_netscape_html(bookmarks))

lines = [
'<DT><A HREF="https://example.com/1" ADD_DATE="1" LAST_MODIFIED="11" PRIVATE="1" TOREAD="0" TAGS="">Title 1</A>',
Expand All @@ -89,15 +89,15 @@ def test_export_bookmarks(self):
'<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)
self.assertIn("\r\n".join(lines), html)

def test_escape_html(self):
bookmark = self.setup_bookmark(
title="<style>: The Style Information element",
description="The <style> HTML element contains style information for a document, or part of a document.",
notes="Interesting notes about the <style> HTML element.",
)
html = exporter.export_netscape_html([bookmark])
html = "\r\n".join(exporter.export_netscape_html([bookmark]))

self.assertIn("&lt;style&gt;: The Style Information element", html)
self.assertIn(
Expand All @@ -111,4 +111,4 @@ def test_handle_empty_values(self):
bookmark.title = ""
bookmark.description = ""
bookmark.save()
exporter.export_netscape_html([bookmark])
"\r\n".join(exporter.export_netscape_html([bookmark]))
10 changes: 6 additions & 4 deletions bookmarks/views/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
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
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import render
from django.urls import reverse
Expand Down Expand Up @@ -233,14 +232,17 @@ def bookmark_import(request):
def bookmark_export(request):
# noinspection PyBroadException
try:
bookmarks = Bookmark.objects.filter(owner=request.user)
# Prefetch tags to prevent n+1 queries
prefetch_related_objects(bookmarks, "tags")
bookmarks = Bookmark.objects.filter(owner=request.user).prefetch_related("tags")
file_content = exporter.export_netscape_html(bookmarks)
def _newline_appended():
for line in file_content:
yield line
yield "\r\n"

response = HttpResponse(content_type="text/plain; charset=UTF-8")
response["Content-Disposition"] = 'attachment; filename="bookmarks.html"'
response.write(file_content)
response.writelines(_newline_appended())

return response
except:
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.