mirror of
https://github.com/sissbruecker/linkding
synced 2024-11-10 06:04:15 +00:00
Add user profile endpoint (#541)
* feat: Implement UserProfile serializer and add API endpoint per #457 * chore: Document API addition * Address review comments --------- Co-authored-by: fkulla <mail@florian.direct>
This commit is contained in:
parent
41f79e35a0
commit
3e4f08f51b
5 changed files with 115 additions and 3 deletions
|
@ -5,7 +5,7 @@ from rest_framework.response import Response
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
from bookmarks import queries
|
from bookmarks import queries
|
||||||
from bookmarks.api.serializers import BookmarkSerializer, TagSerializer
|
from bookmarks.api.serializers import BookmarkSerializer, TagSerializer, UserProfileSerializer
|
||||||
from bookmarks.models import Bookmark, BookmarkSearch, Tag, User
|
from bookmarks.models import Bookmark, BookmarkSearch, Tag, User
|
||||||
from bookmarks.services.bookmarks import archive_bookmark, unarchive_bookmark, website_loader
|
from bookmarks.services.bookmarks import archive_bookmark, unarchive_bookmark, website_loader
|
||||||
from bookmarks.services.website_loader import WebsiteMetadata
|
from bookmarks.services.website_loader import WebsiteMetadata
|
||||||
|
@ -108,6 +108,13 @@ class TagViewSet(viewsets.GenericViewSet,
|
||||||
return {'user': self.request.user}
|
return {'user': self.request.user}
|
||||||
|
|
||||||
|
|
||||||
|
class UserViewSet(viewsets.GenericViewSet):
|
||||||
|
@action(methods=['get'], detail=False)
|
||||||
|
def profile(self, request):
|
||||||
|
return Response(UserProfileSerializer(request.user.profile).data)
|
||||||
|
|
||||||
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r'bookmarks', BookmarkViewSet, basename='bookmark')
|
router.register(r'bookmarks', BookmarkViewSet, basename='bookmark')
|
||||||
router.register(r'tags', TagViewSet, basename='tag')
|
router.register(r'tags', TagViewSet, basename='tag')
|
||||||
|
router.register(r'user', UserViewSet, basename='user')
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.db.models import prefetch_related_objects
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.serializers import ListSerializer
|
from rest_framework.serializers import ListSerializer
|
||||||
|
|
||||||
from bookmarks.models import Bookmark, Tag, build_tag_string
|
from bookmarks.models import Bookmark, Tag, build_tag_string, UserProfile
|
||||||
from bookmarks.services.bookmarks import create_bookmark, update_bookmark
|
from bookmarks.services.bookmarks import create_bookmark, update_bookmark
|
||||||
from bookmarks.services.tags import get_or_create_tag
|
from bookmarks.services.tags import get_or_create_tag
|
||||||
|
|
||||||
|
@ -89,3 +89,21 @@ class TagSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
return get_or_create_tag(validated_data['name'], self.context['user'])
|
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",
|
||||||
|
]
|
||||||
|
|
|
@ -6,8 +6,9 @@ from django.contrib.auth.models import User
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from bookmarks.models import Bookmark
|
from bookmarks.models import Bookmark, BookmarkSearch, UserProfile
|
||||||
from bookmarks.services import website_loader
|
from bookmarks.services import website_loader
|
||||||
from bookmarks.services.website_loader import WebsiteMetadata
|
from bookmarks.services.website_loader import WebsiteMetadata
|
||||||
from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin
|
from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin
|
||||||
|
@ -644,3 +645,49 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
|
||||||
check_url = urllib.parse.quote_plus(inaccessible_bookmark.url)
|
check_url = urllib.parse.quote_plus(inaccessible_bookmark.url)
|
||||||
response = self.get(f'{url}?url={check_url}', expected_status_code=status.HTTP_200_OK)
|
response = self.get(f'{url}?url={check_url}', expected_status_code=status.HTTP_200_OK)
|
||||||
self.assertIsNone(response.data['bookmark'])
|
self.assertIsNone(response.data['bookmark'])
|
||||||
|
|
||||||
|
def assertUserProfile(self, response: Response, profile: UserProfile):
|
||||||
|
self.assertEqual(response.data['theme'], profile.theme)
|
||||||
|
self.assertEqual(response.data['bookmark_date_display'], profile.bookmark_date_display)
|
||||||
|
self.assertEqual(response.data['bookmark_link_target'], profile.bookmark_link_target)
|
||||||
|
self.assertEqual(response.data['web_archive_integration'], profile.web_archive_integration)
|
||||||
|
self.assertEqual(response.data['tag_search'], profile.tag_search)
|
||||||
|
self.assertEqual(response.data['enable_sharing'], profile.enable_sharing)
|
||||||
|
self.assertEqual(response.data['enable_public_sharing'], profile.enable_public_sharing)
|
||||||
|
self.assertEqual(response.data['enable_favicons'], profile.enable_favicons)
|
||||||
|
self.assertEqual(response.data['display_url'], profile.display_url)
|
||||||
|
self.assertEqual(response.data['permanent_notes'], profile.permanent_notes)
|
||||||
|
self.assertEqual(response.data['search_preferences'], profile.search_preferences)
|
||||||
|
|
||||||
|
def test_user_profile(self):
|
||||||
|
self.authenticate()
|
||||||
|
|
||||||
|
# default profile
|
||||||
|
profile = self.user.profile
|
||||||
|
url = reverse('bookmarks:user-profile')
|
||||||
|
response = self.get(url, expected_status_code=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
self.assertUserProfile(response, profile)
|
||||||
|
|
||||||
|
# update profile
|
||||||
|
profile.theme = 'dark'
|
||||||
|
profile.bookmark_date_display = 'absolute'
|
||||||
|
profile.bookmark_link_target = '_self'
|
||||||
|
profile.web_archive_integration = 'enabled'
|
||||||
|
profile.tag_search = 'lax'
|
||||||
|
profile.enable_sharing = True
|
||||||
|
profile.enable_public_sharing = True
|
||||||
|
profile.enable_favicons = True
|
||||||
|
profile.display_url = True
|
||||||
|
profile.permanent_notes = True
|
||||||
|
profile.search_preferences = {
|
||||||
|
'sort': BookmarkSearch.SORT_TITLE_ASC,
|
||||||
|
'shared': BookmarkSearch.FILTER_SHARED_OFF,
|
||||||
|
'unread': BookmarkSearch.FILTER_UNREAD_YES,
|
||||||
|
}
|
||||||
|
profile.save()
|
||||||
|
|
||||||
|
url = reverse('bookmarks:user-profile')
|
||||||
|
response = self.get(url, expected_status_code=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
self.assertUserProfile(response, profile)
|
||||||
|
|
|
@ -111,3 +111,11 @@ class BookmarksApiPermissionsTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
|
||||||
|
|
||||||
self.authenticate()
|
self.authenticate()
|
||||||
self.get(f'{url}?url={check_url}', expected_status_code=status.HTTP_200_OK)
|
self.get(f'{url}?url={check_url}', expected_status_code=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
def test_user_profile_requires_authentication(self):
|
||||||
|
url = reverse('bookmarks:user-profile')
|
||||||
|
|
||||||
|
self.get(url, expected_status_code=status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
|
self.authenticate()
|
||||||
|
self.get(url, expected_status_code=status.HTTP_200_OK)
|
||||||
|
|
32
docs/API.md
32
docs/API.md
|
@ -236,3 +236,35 @@ Example payload:
|
||||||
"name": "example"
|
"name": "example"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### User
|
||||||
|
|
||||||
|
**Profile**
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/user/profile/
|
||||||
|
```
|
||||||
|
|
||||||
|
User preferences.
|
||||||
|
|
||||||
|
Example response:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"theme": "auto",
|
||||||
|
"bookmark_date_display": "relative",
|
||||||
|
"bookmark_link_target": "_blank",
|
||||||
|
"web_archive_integration": "enabled",
|
||||||
|
"tag_search": "lax",
|
||||||
|
"enable_sharing": true,
|
||||||
|
"enable_public_sharing": true,
|
||||||
|
"enable_favicons": false,
|
||||||
|
"display_url": false,
|
||||||
|
"permanent_notes": false,
|
||||||
|
"search_preferences": {
|
||||||
|
"sort": "title_asc",
|
||||||
|
"shared": "off",
|
||||||
|
"unread": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
Loading…
Reference in a new issue