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:
Sascha Ißbrücker 2023-10-01 21:57:32 +02:00 committed by GitHub
parent 41f79e35a0
commit 3e4f08f51b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 115 additions and 3 deletions

View file

@ -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')

View file

@ -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",
]

View file

@ -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)

View file

@ -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)

View file

@ -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"
}
}
```