Allow bookmarks to have empty title and description (#843)

* add migration for merging fields

* remove usage of website title and description

* keep empty website title and description in API for compatibility

* restore scraping in API and add option for disabling it

* document API scraping behavior

* remove deprecated fields from API docs

* improve form layout

* cleanup migration

* cleanup website loader

* update tests
This commit is contained in:
Sascha Ißbrücker
2024-09-22 07:52:00 +02:00
committed by GitHub
parent afa57aa10b
commit fe7ddbe645
22 changed files with 411 additions and 366 deletions

View File

@@ -41,8 +41,6 @@ class BookmarkFactoryMixin:
title: str = None,
description: str = "",
notes: str = "",
website_title: str = "",
website_description: str = "",
web_archive_snapshot_url: str = "",
favicon_file: str = "",
preview_image_file: str = "",
@@ -64,8 +62,6 @@ class BookmarkFactoryMixin:
title=title,
description=description,
notes=notes,
website_title=website_title,
website_description=website_description,
date_added=added,
date_modified=timezone.now(),
owner=user,

View File

@@ -150,16 +150,8 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
self.assertIsNotNone(title)
self.assertEqual(title.text.strip(), bookmark.title)
# with website title
bookmark = self.setup_bookmark(title="", website_title="Website title")
soup = self.get_index_details_modal(bookmark)
title = soup.find("h2")
self.assertIsNotNone(title)
self.assertEqual(title.text.strip(), bookmark.website_title)
# with URL only
bookmark = self.setup_bookmark(title="", website_title="")
bookmark = self.setup_bookmark(title="")
soup = self.get_index_details_modal(bookmark)
title = soup.find("h2")
@@ -478,7 +470,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def test_description(self):
# without description
bookmark = self.setup_bookmark(description="", website_description="")
bookmark = self.setup_bookmark(description="")
soup = self.get_index_details_modal(bookmark)
section = self.find_section(soup, "Description")
@@ -491,15 +483,6 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
section = self.get_section(soup, "Description")
self.assertEqual(section.text.strip(), bookmark.description)
# with website description
bookmark = self.setup_bookmark(
description="", website_description="Website description"
)
soup = self.get_index_details_modal(bookmark)
section = self.get_section(soup, "Description")
self.assertEqual(section.text.strip(), bookmark.website_description)
def test_notes(self):
# without notes
bookmark = self.setup_bookmark()

View File

@@ -80,8 +80,6 @@ class BookmarkEditViewTestCase(TestCase, BookmarkFactoryMixin):
title="edited title",
description="edited description",
notes="edited notes",
website_title="website title",
website_description="website description",
)
response = self.client.get(reverse("bookmarks:edit", args=[bookmark.id]))
@@ -114,7 +112,7 @@ class BookmarkEditViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertInHTML(
f"""
<textarea name="description" cols="40" rows="2" class="form-input" id="id_description">
<textarea name="description" cols="40" rows="3" class="form-input" id="id_description">
{bookmark.description}
</textarea>
""",
@@ -130,22 +128,6 @@ class BookmarkEditViewTestCase(TestCase, BookmarkFactoryMixin):
html,
)
self.assertInHTML(
f"""
<input type="hidden" name="website_title" id="id_website_title"
value="{bookmark.website_title}">
""",
html,
)
self.assertInHTML(
f"""
<input type="hidden" name="website_description" id="id_website_description"
value="{bookmark.website_description}">
""",
html,
)
def test_should_redirect_to_return_url(self):
bookmark = self.setup_bookmark()
form_data = self.create_form_data()

View File

@@ -96,7 +96,7 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertInHTML(
'<textarea name="description" class="form-input" cols="40" '
'rows="2" id="id_description">Example Site Description</textarea>',
'rows="3" id="id_description">Example Site Description</textarea>',
html,
)
@@ -115,6 +115,9 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin):
</summary>
<label for="id_notes" class="text-assistive">Notes</label>
<textarea name="notes" cols="40" rows="8" class="form-input" id="id_notes">**Find** more info [here](http://example.com)</textarea>
<div class="form-input-hint">
Additional notes, supports Markdown.
</div>
</details>
""",
html,

View File

@@ -33,8 +33,6 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
expectation["title"] = bookmark.title
expectation["description"] = bookmark.description
expectation["notes"] = bookmark.notes
expectation["website_title"] = bookmark.website_title
expectation["website_description"] = bookmark.website_description
expectation["web_archive_snapshot_url"] = bookmark.web_archive_snapshot_url
expectation["favicon_url"] = (
f"http://testserver/static/{bookmark.favicon_file}"
@@ -56,6 +54,8 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
expectation["date_modified"] = bookmark.date_modified.isoformat().replace(
"+00:00", "Z"
)
expectation["website_title"] = None
expectation["website_description"] = None
expectations.append(expectation)
for data in data_list:
@@ -87,6 +87,19 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
)
self.assertBookmarkListEqual(response.data["results"], bookmarks)
def test_list_bookmarks_returns_none_for_website_title_and_description(self):
self.authenticate()
bookmark = self.setup_bookmark()
bookmark.website_title = "Website title"
bookmark.website_description = "Website description"
bookmark.save()
response = self.get(
reverse("bookmarks:bookmark-list"), expected_status_code=status.HTTP_200_OK
)
self.assertIsNone(response.data["results"][0]["website_title"])
self.assertIsNone(response.data["results"][0]["website_description"])
def test_list_bookmarks_does_not_return_archived_bookmarks(self):
self.authenticate()
bookmarks = self.setup_numbered_bookmarks(5)
@@ -382,6 +395,44 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
self.assertEqual(bookmark.tags.filter(name=data["tag_names"][0]).count(), 1)
self.assertEqual(bookmark.tags.filter(name=data["tag_names"][1]).count(), 1)
def test_create_bookmark_enhances_with_metadata_by_default(self):
self.authenticate()
data = {"url": "https://example.com/"}
with patch.object(website_loader, "load_website_metadata") as mock_load:
mock_load.return_value = WebsiteMetadata(
url="https://example.com/",
title="Website title",
description="Website description",
preview_image=None,
)
self.post(reverse("bookmarks:bookmark-list"), data, status.HTTP_201_CREATED)
bookmark = Bookmark.objects.get(url=data["url"])
self.assertEqual(bookmark.title, "Website title")
self.assertEqual(bookmark.description, "Website description")
def test_create_bookmark_does_not_enhance_with_metadata_if_scraping_is_disabled(
self,
):
self.authenticate()
data = {"url": "https://example.com/"}
with patch.object(website_loader, "load_website_metadata") as mock_load:
mock_load.return_value = WebsiteMetadata(
url="https://example.com/",
title="Website title",
description="Website description",
preview_image=None,
)
self.post(
reverse("bookmarks:bookmark-list") + "?disable_scraping",
data,
status.HTTP_201_CREATED,
)
bookmark = Bookmark.objects.get(url=data["url"])
self.assertEqual(bookmark.title, "")
self.assertEqual(bookmark.description, "")
def test_create_bookmark_with_same_url_updates_existing_bookmark(self):
self.authenticate()
@@ -775,18 +826,24 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
"http://testserver/static/preview.png", bookmark_data["preview_image_url"]
)
def test_check_returns_existing_metadata_if_url_is_bookmarked(self):
def test_check_returns_scraped_metadata_if_url_is_bookmarked(self):
self.authenticate()
bookmark = self.setup_bookmark(
self.setup_bookmark(
url="https://example.com",
website_title="Existing title",
website_description="Existing description",
)
with patch.object(
website_loader, "load_website_metadata"
) as mock_load_website_metadata:
expected_metadata = WebsiteMetadata(
"https://example.com",
"Scraped metadata",
"Scraped description",
"https://example.com/preview.png",
)
mock_load_website_metadata.return_value = expected_metadata
url = reverse("bookmarks:bookmark-check")
check_url = urllib.parse.quote_plus("https://example.com")
response = self.get(
@@ -794,12 +851,11 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
)
metadata = response.data["metadata"]
mock_load_website_metadata.assert_not_called()
self.assertIsNotNone(metadata)
self.assertEqual(bookmark.url, metadata["url"])
self.assertEqual(bookmark.website_title, metadata["title"])
self.assertEqual(bookmark.website_description, metadata["description"])
self.assertIsNone(metadata["preview_image"])
self.assertEqual(expected_metadata.url, metadata["url"])
self.assertEqual(expected_metadata.title, metadata["title"])
self.assertEqual(expected_metadata.description, metadata["description"])
self.assertEqual(expected_metadata.preview_image, metadata["preview_image"])
def test_check_returns_no_auto_tags_if_none_configured(self):
self.authenticate()

View File

@@ -8,15 +8,9 @@ class BookmarkTestCase(TestCase):
def test_bookmark_resolved_title(self):
bookmark = Bookmark(
title="Custom title",
website_title="Website title",
url="https://example.com",
)
self.assertEqual(bookmark.resolved_title, "Custom title")
bookmark = Bookmark(
title="", website_title="Website title", url="https://example.com"
)
self.assertEqual(bookmark.resolved_title, "Website title")
bookmark = Bookmark(title="", website_title="", url="https://example.com")
bookmark = Bookmark(title="", url="https://example.com")
self.assertEqual(bookmark.resolved_title, "https://example.com")

View File

@@ -25,8 +25,8 @@ from bookmarks.services.bookmarks import (
share_bookmarks,
unshare_bookmarks,
upload_asset,
enhance_with_website_metadata,
)
from bookmarks.services.website_loader import WebsiteMetadata
from bookmarks.tests.helpers import BookmarkFactoryMixin
User = get_user_model()
@@ -37,22 +37,14 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
def setUp(self) -> None:
self.get_or_create_test_user()
def test_create_should_update_website_metadata(self):
def test_create_should_not_update_website_metadata(self):
with patch.object(
website_loader, "load_website_metadata"
) as mock_load_website_metadata:
expected_metadata = WebsiteMetadata(
"https://example.com",
"Website title",
"Website description",
"https://example.com/preview.png",
)
mock_load_website_metadata.return_value = expected_metadata
bookmark_data = Bookmark(
url="https://example.com",
title="Updated Title",
description="Updated description",
title="Initial Title",
description="Initial description",
unread=True,
shared=True,
is_archived=True,
@@ -62,10 +54,9 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
)
created_bookmark.refresh_from_db()
self.assertEqual(expected_metadata.title, created_bookmark.website_title)
self.assertEqual(
expected_metadata.description, created_bookmark.website_description
)
self.assertEqual("Initial Title", created_bookmark.title)
self.assertEqual("Initial description", created_bookmark.description)
mock_load_website_metadata.assert_not_called()
def test_create_should_update_existing_bookmark_with_same_url(self):
original_bookmark = self.setup_bookmark(
@@ -164,37 +155,28 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
mock_create_web_archive_snapshot.assert_not_called()
def test_update_should_update_website_metadata_if_url_did_change(self):
with patch.object(
website_loader, "load_website_metadata"
) as mock_load_website_metadata:
expected_metadata = WebsiteMetadata(
"https://example.com/updated",
"Updated website title",
"Updated website description",
"https://example.com/preview.png",
)
mock_load_website_metadata.return_value = expected_metadata
bookmark = self.setup_bookmark()
bookmark.url = "https://example.com/updated"
update_bookmark(bookmark, "tag1,tag2", self.user)
bookmark.refresh_from_db()
mock_load_website_metadata.assert_called_once()
self.assertEqual(expected_metadata.title, bookmark.website_title)
self.assertEqual(
expected_metadata.description, bookmark.website_description
)
def test_update_should_not_update_website_metadata_if_url_did_not_change(self):
def test_update_should_not_update_website_metadata(self):
with patch.object(
website_loader, "load_website_metadata"
) as mock_load_website_metadata:
bookmark = self.setup_bookmark()
bookmark.title = "updated title"
update_bookmark(bookmark, "tag1,tag2", self.user)
bookmark.refresh_from_db()
self.assertEqual("updated title", bookmark.title)
mock_load_website_metadata.assert_not_called()
def test_update_should_not_update_website_metadata_if_url_did_change(self):
with patch.object(
website_loader, "load_website_metadata"
) as mock_load_website_metadata:
bookmark = self.setup_bookmark(title="initial title")
bookmark.url = "https://example.com/updated"
update_bookmark(bookmark, "tag1,tag2", self.user)
bookmark.refresh_from_db()
self.assertEqual("initial title", bookmark.title)
mock_load_website_metadata.assert_not_called()
def test_update_should_update_favicon(self):
@@ -914,3 +896,61 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
self.assertIsNone(asset.file_size)
self.assertEqual(BookmarkAsset.STATUS_FAILURE, asset.status)
self.assertEqual("", asset.file)
def test_enhance_with_website_metadata(self):
bookmark = self.setup_bookmark(url="https://example.com")
with patch.object(
website_loader, "load_website_metadata"
) as mock_load_website_metadata:
mock_load_website_metadata.return_value = website_loader.WebsiteMetadata(
url="https://example.com",
title="Website title",
description="Website description",
preview_image=None,
)
# missing title and description
bookmark.title = ""
bookmark.description = ""
bookmark.save()
enhance_with_website_metadata(bookmark)
bookmark.refresh_from_db()
self.assertEqual("Website title", bookmark.title)
self.assertEqual("Website description", bookmark.description)
# missing title only
bookmark.title = ""
bookmark.description = "Initial description"
bookmark.save()
enhance_with_website_metadata(bookmark)
bookmark.refresh_from_db()
self.assertEqual("Website title", bookmark.title)
self.assertEqual("Initial description", bookmark.description)
# missing description only
bookmark.title = "Initial title"
bookmark.description = ""
bookmark.save()
enhance_with_website_metadata(bookmark)
bookmark.refresh_from_db()
self.assertEqual("Initial title", bookmark.title)
self.assertEqual("Website description", bookmark.description)
# metadata returns None
mock_load_website_metadata.return_value = website_loader.WebsiteMetadata(
url="https://example.com",
title=None,
description=None,
preview_image=None,
)
bookmark.title = ""
bookmark.description = ""
bookmark.save()
enhance_with_website_metadata(bookmark)
bookmark.refresh_from_db()
self.assertEqual("", bookmark.title)
self.assertEqual("", bookmark.description)

View File

@@ -98,7 +98,5 @@ class ExporterTestCase(TestCase, BookmarkFactoryMixin):
bookmark = self.setup_bookmark()
bookmark.title = ""
bookmark.description = ""
bookmark.website_title = None
bookmark.website_description = None
bookmark.save()
exporter.export_netscape_html([bookmark])

View File

@@ -31,11 +31,18 @@ class FeedsTestCase(TestCase, BookmarkFactoryMixin):
for tag in bookmark.tag_names:
categories.append(f"<category>{tag}</category>")
if bookmark.resolved_description:
expected_description = (
f"<description>{bookmark.resolved_description}</description>"
)
else:
expected_description = "<description/>"
expected_item = (
"<item>"
f"<title>{bookmark.resolved_title}</title>"
f"<link>{bookmark.url}</link>"
f"<description>{bookmark.resolved_description}</description>"
f"{expected_description}"
f"<pubDate>{rfc2822_date(bookmark.date_added)}</pubDate>"
f"<guid>{bookmark.url}</guid>"
f"{''.join(categories)}"
@@ -63,7 +70,7 @@ class FeedsTestCase(TestCase, BookmarkFactoryMixin):
def test_all_returns_all_unarchived_bookmarks(self):
bookmarks = [
self.setup_bookmark(description="test description"),
self.setup_bookmark(website_description="test website description"),
self.setup_bookmark(description=""),
self.setup_bookmark(unread=True, description="test description"),
]
self.setup_bookmark(is_archived=True)
@@ -118,9 +125,7 @@ class FeedsTestCase(TestCase, BookmarkFactoryMixin):
unread_bookmarks = [
self.setup_bookmark(unread=True, description="test description"),
self.setup_bookmark(
unread=True, website_description="test website description"
),
self.setup_bookmark(unread=True, description=""),
self.setup_bookmark(unread=True, description="test description"),
]

View File

@@ -36,14 +36,6 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(description=random_sentence(including_word="TERM1")),
self.setup_bookmark(notes=random_sentence(including_word="term1")),
self.setup_bookmark(notes=random_sentence(including_word="TERM1")),
self.setup_bookmark(website_title=random_sentence(including_word="term1")),
self.setup_bookmark(website_title=random_sentence(including_word="TERM1")),
self.setup_bookmark(
website_description=random_sentence(including_word="term1")
),
self.setup_bookmark(
website_description=random_sentence(including_word="TERM1")
),
]
self.term1_term2_bookmarks = [
self.setup_bookmark(url="http://example.com/term1/term2"),
@@ -55,30 +47,16 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
description=random_sentence(including_word="term1"),
title=random_sentence(including_word="term2"),
),
self.setup_bookmark(
website_title=random_sentence(including_word="term1"),
title=random_sentence(including_word="term2"),
),
self.setup_bookmark(
website_description=random_sentence(including_word="term1"),
title=random_sentence(including_word="term2"),
),
]
self.tag1_bookmarks = [
self.setup_bookmark(tags=[tag1]),
self.setup_bookmark(title=random_sentence(), tags=[tag1]),
self.setup_bookmark(description=random_sentence(), tags=[tag1]),
self.setup_bookmark(website_title=random_sentence(), tags=[tag1]),
self.setup_bookmark(website_description=random_sentence(), tags=[tag1]),
]
self.tag1_as_term_bookmarks = [
self.setup_bookmark(url="http://example.com/tag1"),
self.setup_bookmark(title=random_sentence(including_word="tag1")),
self.setup_bookmark(description=random_sentence(including_word="tag1")),
self.setup_bookmark(website_title=random_sentence(including_word="tag1")),
self.setup_bookmark(
website_description=random_sentence(including_word="tag1")
),
]
self.term1_tag1_bookmarks = [
self.setup_bookmark(url="http://example.com/term1", tags=[tag1]),
@@ -88,12 +66,6 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(
description=random_sentence(including_word="term1"), tags=[tag1]
),
self.setup_bookmark(
website_title=random_sentence(including_word="term1"), tags=[tag1]
),
self.setup_bookmark(
website_description=random_sentence(including_word="term1"), tags=[tag1]
),
]
self.tag2_bookmarks = [
self.setup_bookmark(tags=[tag2]),
@@ -136,22 +108,6 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(
notes=random_sentence(including_word="TERM1"), tags=[self.setup_tag()]
),
self.setup_bookmark(
website_title=random_sentence(including_word="term1"),
tags=[self.setup_tag()],
),
self.setup_bookmark(
website_title=random_sentence(including_word="TERM1"),
tags=[self.setup_tag()],
),
self.setup_bookmark(
website_description=random_sentence(including_word="term1"),
tags=[self.setup_tag()],
),
self.setup_bookmark(
website_description=random_sentence(including_word="TERM1"),
tags=[self.setup_tag()],
),
]
self.term1_term2_bookmarks = [
self.setup_bookmark(
@@ -167,16 +123,6 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
title=random_sentence(including_word="term2"),
tags=[self.setup_tag()],
),
self.setup_bookmark(
website_title=random_sentence(including_word="term1"),
title=random_sentence(including_word="term2"),
tags=[self.setup_tag()],
),
self.setup_bookmark(
website_description=random_sentence(including_word="term1"),
title=random_sentence(including_word="term2"),
tags=[self.setup_tag()],
),
]
self.tag1_bookmarks = [
self.setup_bookmark(tags=[tag1, self.setup_tag()]),
@@ -184,21 +130,11 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(
description=random_sentence(), tags=[tag1, self.setup_tag()]
),
self.setup_bookmark(
website_title=random_sentence(), tags=[tag1, self.setup_tag()]
),
self.setup_bookmark(
website_description=random_sentence(), tags=[tag1, self.setup_tag()]
),
]
self.tag1_as_term_bookmarks = [
self.setup_bookmark(url="http://example.com/tag1"),
self.setup_bookmark(title=random_sentence(including_word="tag1")),
self.setup_bookmark(description=random_sentence(including_word="tag1")),
self.setup_bookmark(website_title=random_sentence(including_word="tag1")),
self.setup_bookmark(
website_description=random_sentence(including_word="tag1")
),
]
self.term1_tag1_bookmarks = [
self.setup_bookmark(
@@ -212,14 +148,6 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
description=random_sentence(including_word="term1"),
tags=[tag1, self.setup_tag()],
),
self.setup_bookmark(
website_title=random_sentence(including_word="term1"),
tags=[tag1, self.setup_tag()],
),
self.setup_bookmark(
website_description=random_sentence(including_word="term1"),
tags=[tag1, self.setup_tag()],
),
]
self.tag2_bookmarks = [
self.setup_bookmark(tags=[tag2, self.setup_tag()]),
@@ -1260,30 +1188,18 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
self.setup_bookmark(title="A_1_2"),
self.setup_bookmark(title="b_1_1"),
self.setup_bookmark(title="B_1_2"),
self.setup_bookmark(title="", website_title="a_2_1"),
self.setup_bookmark(title="", website_title="A_2_2"),
self.setup_bookmark(title="", website_title="b_2_1"),
self.setup_bookmark(title="", website_title="B_2_2"),
self.setup_bookmark(title="", website_title="", url="a_3_1"),
self.setup_bookmark(title="", website_title="", url="A_3_2"),
self.setup_bookmark(title="", website_title="", url="b_3_1"),
self.setup_bookmark(title="", website_title="", url="B_3_2"),
self.setup_bookmark(title="a_4_1", website_title="0"),
self.setup_bookmark(title="A_4_2", website_title="0"),
self.setup_bookmark(title="b_4_1", website_title="0"),
self.setup_bookmark(title="B_4_2", website_title="0"),
self.setup_bookmark(title="", url="a_3_1"),
self.setup_bookmark(title="", url="A_3_2"),
self.setup_bookmark(title="", url="b_3_1"),
self.setup_bookmark(title="", url="B_3_2"),
self.setup_bookmark(title="a_5_1", url="0"),
self.setup_bookmark(title="A_5_2", url="0"),
self.setup_bookmark(title="b_5_1", url="0"),
self.setup_bookmark(title="B_5_2", url="0"),
self.setup_bookmark(title="", website_title="a_6_1", url="0"),
self.setup_bookmark(title="", website_title="A_6_2", url="0"),
self.setup_bookmark(title="", website_title="b_6_1", url="0"),
self.setup_bookmark(title="", website_title="B_6_2", url="0"),
self.setup_bookmark(title="a_7_1", website_title="0", url="0"),
self.setup_bookmark(title="A_7_2", website_title="0", url="0"),
self.setup_bookmark(title="b_7_1", website_title="0", url="0"),
self.setup_bookmark(title="B_7_2", website_title="0", url="0"),
self.setup_bookmark(title="", url="0"),
self.setup_bookmark(title="", url="0"),
self.setup_bookmark(title="", url="0"),
self.setup_bookmark(title="", url="0"),
]
return bookmarks