Allow setting date_added and date_modified for new bookmarks through REST API (#1063)

* Make date_added and date_modified optionally writable fields for the POST /api/bookmarks/ API

* Update as per PR feedback to avoid double-save; add test coverage

* Remove blank line

* improve tests

---------

Co-authored-by: Justin.Mason <Justin.Mason@messagegears.com>
Co-authored-by: Sascha Ißbrücker <sascha.issbruecker@gmail.com>
This commit is contained in:
Justin Mason
2026-01-05 18:12:14 +00:00
committed by GitHub
parent 184e4baa84
commit 4f26c3483b
4 changed files with 83 additions and 5 deletions

View File

@@ -86,8 +86,6 @@ class BookmarkSerializer(serializers.ModelSerializer):
"favicon_url",
"preview_image_url",
"tag_names",
"date_added",
"date_modified",
"website_title",
"website_description",
]
@@ -102,6 +100,9 @@ class BookmarkSerializer(serializers.ModelSerializer):
# Add dummy website title and description fields for backwards compatibility but keep them empty
website_title = EmtpyField()
website_description = EmtpyField()
# these are optional
date_added = serializers.DateTimeField(required=False)
date_modified = serializers.DateTimeField(required=False)
def get_favicon_url(self, obj: Bookmark):
if not obj.favicon_file:

View File

@@ -26,9 +26,12 @@ def create_bookmark(
# Set currently logged in user as owner
bookmark.owner = current_user
# Set dates
bookmark.date_added = timezone.now()
bookmark.date_modified = timezone.now()
# Set dates only if not already provided
# This allows to sync existing dates through the REST API for example
if not bookmark.date_added:
bookmark.date_added = timezone.now()
if not bookmark.date_modified:
bookmark.date_modified = timezone.now()
bookmark.save()
# Update tag list
_update_bookmark_tags(bookmark, tag_string, current_user)

View File

@@ -649,6 +649,37 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
bookmark = Bookmark.objects.get(url=data["url"])
self.assertCountEqual(bookmark.tags.all(), [tag1, tag2])
def test_create_bookmark_should_set_default_dates(self):
self.authenticate()
with patch("bookmarks.services.bookmarks.timezone.now") as mock_now:
fixed_time = timezone.make_aware(datetime.datetime(2024, 1, 15, 12, 0, 0))
mock_now.return_value = fixed_time
data = {"url": "https://example.com/"}
self.post(reverse("linkding:bookmark-list"), data, status.HTTP_201_CREATED)
bookmark = Bookmark.objects.get(url=data["url"])
self.assertEqual(bookmark.date_added, fixed_time)
self.assertEqual(bookmark.date_modified, fixed_time)
def test_create_bookmark_with_date_added(self):
self.authenticate()
date1 = timezone.now() - datetime.timedelta(days=30)
data = {"url": "https://example.com/", "date_added": date1.isoformat()}
self.post(reverse("linkding:bookmark-list"), data, status.HTTP_201_CREATED)
bookmark = Bookmark.objects.get(url=data["url"])
self.assertEqual(bookmark.date_added.isoformat(), date1.isoformat())
def test_create_bookmark_with_date_modified(self):
self.authenticate()
date1 = timezone.now() - datetime.timedelta(days=15)
data = {"url": "https://example.com/", "date_modified": date1.isoformat()}
self.post(reverse("linkding:bookmark-list"), data, status.HTTP_201_CREATED)
bookmark = Bookmark.objects.get(url=data["url"])
self.assertEqual(bookmark.date_modified.isoformat(), date1.isoformat())
def test_get_bookmark(self):
self.authenticate()
bookmark = self.setup_bookmark()

View File

@@ -1,3 +1,4 @@
import datetime
from unittest.mock import patch
from django.test import TestCase
@@ -226,6 +227,48 @@ class BookmarkServiceTestCase(TestCase, BookmarkFactoryMixin):
self.assertCountEqual(bookmark.tags.all(), [tag1, tag2])
def test_create_should_set_default_dates(self):
with patch("bookmarks.services.bookmarks.timezone.now") as mock_now:
fixed_time = timezone.make_aware(datetime.datetime(2024, 1, 15, 12, 0, 0))
mock_now.return_value = fixed_time
bookmark_data = Bookmark(url="https://example.com")
bookmark = create_bookmark(bookmark_data, "", self.user)
bookmark.refresh_from_db()
self.assertEqual(bookmark.date_added, fixed_time)
self.assertEqual(bookmark.date_modified, fixed_time)
def test_create_should_use_provided_date_added(self):
custom_date = timezone.now() - datetime.timedelta(days=30)
bookmark_data = Bookmark(url="https://example.com", date_added=custom_date)
bookmark = create_bookmark(bookmark_data, "", self.user)
bookmark.refresh_from_db()
self.assertEqual(bookmark.date_added, custom_date)
def test_create_should_use_provided_date_modified(self):
custom_date = timezone.now() - datetime.timedelta(days=15)
bookmark_data = Bookmark(url="https://example.com", date_modified=custom_date)
bookmark = create_bookmark(bookmark_data, "", self.user)
bookmark.refresh_from_db()
self.assertEqual(bookmark.date_modified, custom_date)
def test_create_should_use_provided_dates(self):
custom_date_added = timezone.now() - datetime.timedelta(days=30)
custom_date_modified = timezone.now() - datetime.timedelta(days=15)
bookmark_data = Bookmark(
url="https://example.com",
date_added=custom_date_added,
date_modified=custom_date_modified,
)
bookmark = create_bookmark(bookmark_data, "", self.user)
bookmark.refresh_from_db()
self.assertEqual(bookmark.date_added, custom_date_added)
self.assertEqual(bookmark.date_modified, custom_date_modified)
def test_update_should_create_web_archive_snapshot_if_url_did_change(self):
with patch.object(
tasks, "create_web_archive_snapshot"