Repository: sissbruecker/linkding
Pull request #708 (2024-04-17 21:02:50)

Show proper name for bookmark assets in admin
When viewing the detail of a BookmarkAsset in the admin panel, it shows as the default object string.

![image](https://github.com/sissbruecker/linkding/assets/3438974/65fe4a44-cdaf-48df-8ad5-d3b4ae37807c)

I have added a very basic string method, which uses the display_name or if that is not present (as its not mandatory), defaults to a simple string.

**With no display_name**
![image](https://github.com/sissbruecker/linkding/assets/3438974/b08cdd4e-5204-4894-9e11-f9b636156f78)

**With display_name**
![image](https://github.com/sissbruecker/linkding/assets/3438974/91aec7c0-53b2-497c-9b48-384392134445)

This is also reflected on the list page also

![image](https://github.com/sissbruecker/linkding/assets/3438974/77614b13-8b78-4c20-9219-a994c2f44b17)




Patch:
diff --git a/bookmarks/admin.py b/bookmarks/admin.py
index 532c1a9..ca6423a 100644
--- a/bookmarks/admin.py
+++ b/bookmarks/admin.py
@@ -200,9 +200,13 @@ class AdminBookmark(admin.ModelAdmin):
 
 
 class AdminBookmarkAsset(admin.ModelAdmin):
-    list_display = ("display_name", "date_created", "status")
+    @admin.display(description="Display Name")
+    def custom_display_name(self, obj):
+        return str(obj)
+
+    list_display = ("custom_display_name", "date_created", "status")
     search_fields = (
-        "display_name",
+        "custom_display_name",
         "file",
     )
     list_filter = ("status",)
diff --git a/bookmarks/migrations/0033_userprofile_default_mark_unread.py b/bookmarks/migrations/0033_userprofile_default_mark_unread.py
new file mode 100644
index 0000000..e5bcfe5
--- /dev/null
+++ b/bookmarks/migrations/0033_userprofile_default_mark_unread.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.0.3 on 2024-04-17 19:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("bookmarks", "0032_html_snapshots_hint_toast"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="userprofile",
+            name="default_mark_unread",
+            field=models.BooleanField(default=False),
+        ),
+    ]
diff --git a/bookmarks/models.py b/bookmarks/models.py
index c27ae58..96bbb1f 100644
--- a/bookmarks/models.py
+++ b/bookmarks/models.py
@@ -118,6 +118,9 @@ class BookmarkAsset(models.Model):
                 pass
         super().save(*args, **kwargs)
 
+    def __str__(self):
+        return self.display_name or f"Bookmark Asset #{self.pk}"
+
 
 @receiver(post_delete, sender=BookmarkAsset)
 def bookmark_asset_deleted(sender, instance, **kwargs):
@@ -399,6 +402,7 @@ class UserProfile(models.Model):
     custom_css = models.TextField(blank=True, null=False)
     search_preferences = models.JSONField(default=dict, null=False)
     enable_automatic_html_snapshots = models.BooleanField(default=True, null=False)
+    default_mark_unread = models.BooleanField(default=False, null=False)
 
 
 class UserProfileForm(forms.ModelForm):
@@ -422,6 +426,7 @@ class UserProfileForm(forms.ModelForm):
             "display_archive_bookmark_action",
             "display_remove_bookmark_action",
             "permanent_notes",
+            "default_mark_unread",
             "custom_css",
         ]
 
diff --git a/bookmarks/templates/settings/general.html b/bookmarks/templates/settings/general.html
index acec66d..436f404 100644
--- a/bookmarks/templates/settings/general.html
+++ b/bookmarks/templates/settings/general.html
@@ -175,6 +175,17 @@
             <button class="btn mt-2" name="create_missing_html_snapshots">Create missing HTML snapshots</button>
           </div>
         {% endif %}
+        <div class="form-group">
+          <label for="{{ form.default_mark_unread.id_for_label }}" class="form-checkbox">
+            {{ form.default_mark_unread }}
+            <i class="form-icon"></i> Create bookmarks as unread by default
+          </label>
+          <div class="form-input-hint">
+            Sets the default state for the "Mark as unread" option when creating a new bookmark.
+            Setting this option will make all new bookmarks default to unread.
+            This can be overridden when creating each new bookmark.
+          </div>
+        </div>
         <div class="form-group">
           <details {% if form.custom_css.value %}open{% endif %}>
             <summary>Custom CSS</summary>
diff --git a/bookmarks/views/bookmarks.py b/bookmarks/views/bookmarks.py
index 4cc4ee5..513f7bc 100644
--- a/bookmarks/views/bookmarks.py
+++ b/bookmarks/views/bookmarks.py
@@ -188,6 +188,7 @@ def new(request):
     initial_title = request.GET.get("title")
     initial_description = request.GET.get("description")
     initial_auto_close = "auto_close" in request.GET
+    initial_mark_unread = request.user.profile.default_mark_unread
 
     if request.method == "POST":
         form = BookmarkForm(request.POST)
@@ -210,6 +211,8 @@ def new(request):
             form.initial["description"] = initial_description
         if initial_auto_close:
             form.initial["auto_close"] = "true"
+        if initial_mark_unread:
+            form.initial["unread"] = "true"
 
     context = {
         "form": form,


Test patch:
diff --git a/bookmarks/tests/test_bookmark_new_view.py b/bookmarks/tests/test_bookmark_new_view.py
index a8ebd44..cef1812 100644
--- a/bookmarks/tests/test_bookmark_new_view.py
+++ b/bookmarks/tests/test_bookmark_new_view.py
@@ -210,3 +210,25 @@ class BookmarkNewViewTestCase(TestCase, BookmarkFactoryMixin):
         response = self.client.get(reverse("bookmarks:edit", args=[bookmark.id]))
 
         self.assertContains(response, '<details class="notes">', count=1)
+
+    def test_should_not_check_unread_by_default(self):
+        response = self.client.get(reverse("bookmarks:new"))
+        html = response.content.decode()
+
+        self.assertInHTML(
+            '<input type="checkbox" name="unread" id="id_unread">',
+            html,
+        )
+
+    def test_should_check_unread_when_configured_in_profile(self):
+        self.user.profile.default_mark_unread = True
+        self.user.profile.save()
+
+        response = self.client.get(reverse("bookmarks:new"))
+        html = response.content.decode()
+
+        self.assertInHTML(
+            '<input type="checkbox" name="unread" value="true" '
+            'id="id_unread" checked="">',
+            html,
+        )
diff --git a/bookmarks/tests/test_settings_general_view.py b/bookmarks/tests/test_settings_general_view.py
index 3f6c4c7..2598ae7 100644
--- a/bookmarks/tests/test_settings_general_view.py
+++ b/bookmarks/tests/test_settings_general_view.py
@@ -96,6 +96,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
             "display_archive_bookmark_action": False,
             "display_remove_bookmark_action": False,
             "permanent_notes": True,
+            "default_mark_unread": True,
             "custom_css": "body { background-color: #000; }",
         }
         response = self.client.post(reverse("bookmarks:settings.general"), form_data)
@@ -155,6 +156,9 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
         self.assertEqual(
             self.user.profile.permanent_notes, form_data["permanent_notes"]
         )
+        self.assertEqual(
+            self.user.profile.default_mark_unread, form_data["default_mark_unread"]
+        )
         self.assertEqual(self.user.profile.custom_css, form_data["custom_css"])
         self.assertSuccessMessage(html, "Profile updated")