mirror of
https://github.com/sissbruecker/linkding
synced 2024-11-25 21:00:18 +00:00
d1f81fee0e
* prevent creating duplicate URLs on edit * Prevent duplicates when editing
158 lines
5.5 KiB
Python
158 lines
5.5 KiB
Python
from django.db.models import prefetch_related_objects
|
|
from django.templatetags.static import static
|
|
from rest_framework import serializers
|
|
from rest_framework.serializers import ListSerializer
|
|
|
|
from bookmarks.models import Bookmark, Tag, build_tag_string, UserProfile
|
|
from bookmarks.services.bookmarks import (
|
|
create_bookmark,
|
|
update_bookmark,
|
|
enhance_with_website_metadata,
|
|
)
|
|
from bookmarks.services.tags import get_or_create_tag
|
|
|
|
|
|
class TagListField(serializers.ListField):
|
|
child = serializers.CharField()
|
|
|
|
|
|
class BookmarkListSerializer(ListSerializer):
|
|
def to_representation(self, data):
|
|
# Prefetch nested relations to avoid n+1 queries
|
|
prefetch_related_objects(data, "tags")
|
|
|
|
return super().to_representation(data)
|
|
|
|
|
|
class BookmarkSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Bookmark
|
|
fields = [
|
|
"id",
|
|
"url",
|
|
"title",
|
|
"description",
|
|
"notes",
|
|
"web_archive_snapshot_url",
|
|
"favicon_url",
|
|
"preview_image_url",
|
|
"is_archived",
|
|
"unread",
|
|
"shared",
|
|
"tag_names",
|
|
"date_added",
|
|
"date_modified",
|
|
"website_title",
|
|
"website_description",
|
|
]
|
|
read_only_fields = [
|
|
"web_archive_snapshot_url",
|
|
"favicon_url",
|
|
"preview_image_url",
|
|
"tag_names",
|
|
"date_added",
|
|
"date_modified",
|
|
"website_title",
|
|
"website_description",
|
|
]
|
|
list_serializer_class = BookmarkListSerializer
|
|
|
|
# Custom tag_names field to allow passing a list of tag names to create/update
|
|
tag_names = TagListField(required=False)
|
|
# Custom fields to return URLs for favicon and preview image
|
|
favicon_url = serializers.SerializerMethodField()
|
|
preview_image_url = serializers.SerializerMethodField()
|
|
# Add dummy website title and description fields for backwards compatibility but keep them empty
|
|
website_title = serializers.SerializerMethodField()
|
|
website_description = serializers.SerializerMethodField()
|
|
|
|
def get_favicon_url(self, obj: Bookmark):
|
|
if not obj.favicon_file:
|
|
return None
|
|
request = self.context.get("request")
|
|
favicon_file_path = static(obj.favicon_file)
|
|
favicon_url = request.build_absolute_uri(favicon_file_path)
|
|
return favicon_url
|
|
|
|
def get_preview_image_url(self, obj: Bookmark):
|
|
if not obj.preview_image_file:
|
|
return None
|
|
request = self.context.get("request")
|
|
preview_image_file_path = static(obj.preview_image_file)
|
|
preview_image_url = request.build_absolute_uri(preview_image_file_path)
|
|
return preview_image_url
|
|
|
|
def get_website_title(self, obj: Bookmark):
|
|
return None
|
|
|
|
def get_website_description(self, obj: Bookmark):
|
|
return None
|
|
|
|
def create(self, validated_data):
|
|
tag_names = validated_data.pop("tag_names", [])
|
|
tag_string = build_tag_string(tag_names)
|
|
bookmark = Bookmark(**validated_data)
|
|
|
|
saved_bookmark = create_bookmark(bookmark, tag_string, self.context["user"])
|
|
# Unless scraping is explicitly disabled, enhance bookmark with website
|
|
# metadata to preserve backwards compatibility with clients that expect
|
|
# title and description to be populated automatically when left empty
|
|
if not self.context.get("disable_scraping", False):
|
|
enhance_with_website_metadata(saved_bookmark)
|
|
return saved_bookmark
|
|
|
|
def update(self, instance: Bookmark, validated_data):
|
|
tag_names = validated_data.pop("tag_names", instance.tag_names)
|
|
tag_string = build_tag_string(tag_names)
|
|
|
|
for field_name, field in self.fields.items():
|
|
if not field.read_only and field_name in validated_data:
|
|
setattr(instance, field_name, validated_data[field_name])
|
|
|
|
return update_bookmark(instance, tag_string, self.context["user"])
|
|
|
|
def validate(self, attrs):
|
|
# When creating a bookmark, the service logic prevents duplicate URLs by
|
|
# updating the existing bookmark instead. When editing a bookmark,
|
|
# there is no assumption that it would update a different bookmark if
|
|
# the URL is a duplicate, so raise a validation error in that case.
|
|
if self.instance and "url" in attrs:
|
|
is_duplicate = (
|
|
Bookmark.objects.filter(owner=self.instance.owner, url=attrs["url"])
|
|
.exclude(pk=self.instance.pk)
|
|
.exists()
|
|
)
|
|
if is_duplicate:
|
|
raise serializers.ValidationError(
|
|
{"url": "A bookmark with this URL already exists."}
|
|
)
|
|
|
|
return attrs
|
|
|
|
|
|
class TagSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = Tag
|
|
fields = ["id", "name", "date_added"]
|
|
read_only_fields = ["date_added"]
|
|
|
|
def create(self, validated_data):
|
|
return get_or_create_tag(validated_data["name"], self.context["user"])
|
|
|
|
|
|
class UserProfileSerializer(serializers.ModelSerializer):
|
|
class Meta:
|
|
model = UserProfile
|
|
fields = [
|
|
"theme",
|
|
"bookmark_date_display",
|
|
"bookmark_link_target",
|
|
"web_archive_integration",
|
|
"tag_search",
|
|
"enable_sharing",
|
|
"enable_public_sharing",
|
|
"enable_favicons",
|
|
"display_url",
|
|
"permanent_notes",
|
|
"search_preferences",
|
|
]
|