diff --git a/vrobbler/apps/books/api/serializers.py b/vrobbler/apps/books/api/serializers.py new file mode 100644 index 0000000..a5eb2ba --- /dev/null +++ b/vrobbler/apps/books/api/serializers.py @@ -0,0 +1,14 @@ +from books.models import Author, Book +from rest_framework import serializers + + +class AuthorSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Author + fields = "__all__" + + +class BookSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Book + fields = "__all__" diff --git a/vrobbler/apps/books/api/views.py b/vrobbler/apps/books/api/views.py new file mode 100644 index 0000000..6cdb4de --- /dev/null +++ b/vrobbler/apps/books/api/views.py @@ -0,0 +1,19 @@ +from rest_framework import permissions, viewsets + +from books.api.serializers import ( + AuthorSerializer, + BookSerializer, +) +from books.models import Author, Book + + +class AuthorViewSet(viewsets.ModelViewSet): + queryset = Author.objects.all().order_by('-created') + serializer_class = AuthorSerializer + permission_classes = [permissions.IsAuthenticated] + + +class BookViewSet(viewsets.ModelViewSet): + queryset = Book.objects.all().order_by('-created') + serializer_class = BookSerializer + permission_classes = [permissions.IsAuthenticated] diff --git a/vrobbler/apps/books/models.py b/vrobbler/apps/books/models.py index 48fda37..ad53383 100644 --- a/vrobbler/apps/books/models.py +++ b/vrobbler/apps/books/models.py @@ -8,7 +8,8 @@ from django.urls import reverse from django_extensions.db.models import TimeStampedModel from scrobbles.mixins import ScrobblableMixin -from vrobbler.apps.books.utils import lookup_book_from_openlibrary +from books.utils import lookup_book_from_openlibrary +from scrobbles.utils import get_scrobbles_for_media logger = logging.getLogger(__name__) User = get_user_model() @@ -66,3 +67,7 @@ class Book(ScrobblableMixin): logger.warn(f"{self} has no pages, no completion percentage") return 0 return int(self.pages * (self.COMPLETION_PERCENT / 100)) + + def progress_for_user(self, user: User) -> int: + last_scrobble = get_scrobbles_for_media(self, user).last() + return int((last_scrobble.book_pages_read / self.pages) * 100) diff --git a/vrobbler/apps/music/api/__init__.py b/vrobbler/apps/music/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vrobbler/apps/music/api/serializers.py b/vrobbler/apps/music/api/serializers.py new file mode 100644 index 0000000..9730dd4 --- /dev/null +++ b/vrobbler/apps/music/api/serializers.py @@ -0,0 +1,20 @@ +from music.models import Album, Artist, Track +from rest_framework import serializers + + +class ArtistSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Artist + fields = "__all__" + + +class AlbumSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Album + fields = "__all__" + + +class TrackSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Track + fields = "__all__" diff --git a/vrobbler/apps/music/api/views.py b/vrobbler/apps/music/api/views.py new file mode 100644 index 0000000..788ce00 --- /dev/null +++ b/vrobbler/apps/music/api/views.py @@ -0,0 +1,26 @@ +from rest_framework import permissions, viewsets + +from music.api.serializers import ( + TrackSerializer, + ArtistSerializer, + AlbumSerializer, +) +from music.models import Artist, Album, Track + + +class ArtistViewSet(viewsets.ModelViewSet): + queryset = Artist.objects.all().order_by('-created') + serializer_class = ArtistSerializer + permission_classes = [permissions.IsAuthenticated] + + +class AlbumViewSet(viewsets.ModelViewSet): + queryset = Album.objects.all().order_by('-created') + serializer_class = AlbumSerializer + permission_classes = [permissions.IsAuthenticated] + + +class TrackViewSet(viewsets.ModelViewSet): + queryset = Track.objects.all().order_by('-created') + serializer_class = TrackSerializer + permission_classes = [permissions.IsAuthenticated] diff --git a/vrobbler/apps/music/serializers.py b/vrobbler/apps/music/serializers.py new file mode 100644 index 0000000..e5a0d9b --- /dev/null +++ b/vrobbler/apps/music/serializers.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 diff --git a/vrobbler/apps/profiles/api/__init__.py b/vrobbler/apps/profiles/api/__init__.py new file mode 100644 index 0000000..e5a0d9b --- /dev/null +++ b/vrobbler/apps/profiles/api/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 diff --git a/vrobbler/apps/profiles/api/serializers.py b/vrobbler/apps/profiles/api/serializers.py new file mode 100644 index 0000000..5e233b7 --- /dev/null +++ b/vrobbler/apps/profiles/api/serializers.py @@ -0,0 +1,18 @@ +from django.contrib.auth import get_user_model +from rest_framework import serializers + +User = get_user_model() + +from profiles.models import UserProfile + + +class UserSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = User + exclude = ('password',) + + +class UserProfileSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = UserProfile + exclude = ('lastfm_password',) diff --git a/vrobbler/apps/profiles/api/views.py b/vrobbler/apps/profiles/api/views.py new file mode 100644 index 0000000..9779c72 --- /dev/null +++ b/vrobbler/apps/profiles/api/views.py @@ -0,0 +1,28 @@ +from django.contrib.auth import get_user_model + +from rest_framework import permissions, viewsets + +from profiles.api.serializers import UserSerializer, UserProfileSerializer +from profiles.models import UserProfile + +User = get_user_model() + + +class UserViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows users to be viewed or edited. + """ + + queryset = User.objects.all().order_by('-date_joined') + serializer_class = UserSerializer + permission_classes = [permissions.IsAuthenticated] + + +class UserProfileViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows users to be viewed or edited. + """ + + queryset = UserProfile.objects.all().order_by('-created') + serializer_class = UserProfileSerializer + permission_classes = [permissions.IsAuthenticated] diff --git a/vrobbler/apps/scrobbles/api/__init__.py b/vrobbler/apps/scrobbles/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vrobbler/apps/scrobbles/api/serializers.py b/vrobbler/apps/scrobbles/api/serializers.py new file mode 100644 index 0000000..68f2811 --- /dev/null +++ b/vrobbler/apps/scrobbles/api/serializers.py @@ -0,0 +1,33 @@ +from rest_framework import serializers +from scrobbles.models import ( + AudioScrobblerTSVImport, + KoReaderImport, + LastFmImport, + Scrobble, +) + + +class ScrobbleSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Scrobble + fields = "__all__" + + +class KoReaderImportSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = KoReaderImport + fields = "__all__" + + +class AudioScrobblerTSVImportSerializer( + serializers.HyperlinkedModelSerializer +): + class Meta: + model = AudioScrobblerTSVImport + fields = "__all__" + + +class LastFmImportSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = LastFmImport + fields = "__all__" diff --git a/vrobbler/apps/scrobbles/api/views.py b/vrobbler/apps/scrobbles/api/views.py new file mode 100644 index 0000000..3768252 --- /dev/null +++ b/vrobbler/apps/scrobbles/api/views.py @@ -0,0 +1,49 @@ +from rest_framework import permissions, viewsets +from scrobbles.api.serializers import ( + AudioScrobblerTSVImportSerializer, + KoReaderImportSerializer, + LastFmImportSerializer, + ScrobbleSerializer, +) +from scrobbles.models import ( + AudioScrobblerTSVImport, + KoReaderImport, + Scrobble, + LastFmImport, +) + + +class ScrobbleViewSet(viewsets.ModelViewSet): + queryset = Scrobble.objects.all().order_by('-timestamp') + serializer_class = ScrobbleSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return super().get_queryset().filter(user=self.request.user) + + +class KoReaderImportViewSet(viewsets.ModelViewSet): + queryset = KoReaderImport.objects.all().order_by('-created') + serializer_class = KoReaderImportSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return super().get_queryset().filter(user=self.request.user) + + +class AudioScrobblerTSVImportViewSet(viewsets.ModelViewSet): + queryset = AudioScrobblerTSVImport.objects.all().order_by('-created') + serializer_class = AudioScrobblerTSVImportSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return super().get_queryset().filter(user=self.request.user) + + +class LastFmImportViewSet(viewsets.ModelViewSet): + queryset = LastFmImport.objects.all().order_by('-created') + serializer_class = LastFmImportSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return super().get_queryset().filter(user=self.request.user) diff --git a/vrobbler/apps/scrobbles/serializers.py b/vrobbler/apps/scrobbles/serializers.py deleted file mode 100644 index 08771e0..0000000 --- a/vrobbler/apps/scrobbles/serializers.py +++ /dev/null @@ -1,14 +0,0 @@ -from rest_framework import serializers -from scrobbles.models import Scrobble, AudioScrobblerTSVImport - - -class AudioScrobblerTSVImportSerializer(serializers.ModelSerializer): - class Meta: - model = AudioScrobblerTSVImport - fields = ('tsv_file',) - - -class ScrobbleSerializer(serializers.ModelSerializer): - class Meta: - model = Scrobble - fields = "__all__" diff --git a/vrobbler/apps/scrobbles/urls.py b/vrobbler/apps/scrobbles/urls.py index 7754349..aa793b4 100644 --- a/vrobbler/apps/scrobbles/urls.py +++ b/vrobbler/apps/scrobbles/urls.py @@ -4,7 +4,6 @@ from scrobbles import views app_name = 'scrobbles' urlpatterns = [ - path('', views.scrobble_endpoint, name='api-list'), path('finish/', views.scrobble_finish, name='finish'), path('cancel/', views.scrobble_cancel, name='cancel'), path( diff --git a/vrobbler/apps/scrobbles/utils.py b/vrobbler/apps/scrobbles/utils.py index 9b306cc..7a17e8c 100644 --- a/vrobbler/apps/scrobbles/utils.py +++ b/vrobbler/apps/scrobbles/utils.py @@ -1,10 +1,14 @@ import logging from urllib.parse import unquote +from django.contrib.auth import get_user_model + from dateutil.parser import ParserError, parse from django.conf import settings +from django.db import models logger = logging.getLogger(__name__) +User = get_user_model() def convert_to_seconds(run_time: str) -> int: @@ -103,3 +107,11 @@ def check_scrobble_for_finish( if getattr(settings, "KEEP_DETAILED_SCROBBLE_LOGS", False): scrobble.scrobble_log += f"\n{str(scrobble.timestamp)} - {scrobble.playback_position} - {str(scrobble.playback_position_ticks)} - {str(scrobble.percent_played)}%" scrobble.save(update_fields=['scrobble_log']) + + +def get_scrobbles_for_media(media_obj, user: User) -> models.QuerySet: + from scrobbles.models import Scrobble + + if media_obj.__class__.__name__ == 'Book': + media_query = models.Q(book=media_obj) + return Scrobble.objects.filter(media_query, user=user) diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index 8d599ea..423efa6 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -49,10 +49,7 @@ from scrobbles.scrobblers import ( mopidy_scrobble_podcast, mopidy_scrobble_track, ) -from scrobbles.serializers import ( - AudioScrobblerTSVImportSerializer, - ScrobbleSerializer, -) +from scrobbles.api import serializers from scrobbles.tasks import ( process_koreader_import, process_lastfm_import, @@ -215,15 +212,6 @@ def lastfm_import(request): return HttpResponseRedirect(success_url) -@csrf_exempt -@api_view(['GET']) -def scrobble_endpoint(request): - """List all Scrobbles, or create a new Scrobble""" - scrobble = Scrobble.objects.all() - serializer = ScrobbleSerializer(scrobble, many=True) - return Response(serializer.data) - - @csrf_exempt @permission_classes([IsAuthenticated]) @api_view(['POST']) @@ -293,7 +281,9 @@ def import_audioscrobbler_file(request): scrobbles_created = [] # tsv_file = request.FILES[0] - file_serializer = AudioScrobblerTSVImportSerializer(data=request.data) + file_serializer = serializers.AudioScrobblerTSVImportSerializer( + data=request.data + ) if file_serializer.is_valid(): import_file = file_serializer.save() return Response( diff --git a/vrobbler/apps/videos/api/__init__.py b/vrobbler/apps/videos/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vrobbler/apps/videos/api/serializers.py b/vrobbler/apps/videos/api/serializers.py new file mode 100644 index 0000000..62eacf5 --- /dev/null +++ b/vrobbler/apps/videos/api/serializers.py @@ -0,0 +1,14 @@ +from videos.models import Series, Video +from rest_framework import serializers + + +class SeriesSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Series + fields = "__all__" + + +class VideoSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Video + fields = "__all__" diff --git a/vrobbler/apps/videos/api/views.py b/vrobbler/apps/videos/api/views.py new file mode 100644 index 0000000..f9b2503 --- /dev/null +++ b/vrobbler/apps/videos/api/views.py @@ -0,0 +1,19 @@ +from rest_framework import permissions, viewsets + +from videos.api.serializers import ( + SeriesSerializer, + VideoSerializer, +) +from videos.models import Series, Video + + +class SeriesViewSet(viewsets.ModelViewSet): + queryset = Series.objects.all().order_by('-created') + serializer_class = SeriesSerializer + permission_classes = [permissions.IsAuthenticated] + + +class VideoViewSet(viewsets.ModelViewSet): + queryset = Video.objects.all().order_by('-created') + serializer_class = VideoSerializer + permission_classes = [permissions.IsAuthenticated] diff --git a/vrobbler/settings.py b/vrobbler/settings.py index 50cfd07..0777da7 100644 --- a/vrobbler/settings.py +++ b/vrobbler/settings.py @@ -174,19 +174,14 @@ AUTHENTICATION_BACKENDS = [ REST_FRAMEWORK = { "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.AllowAny",), 'DEFAULT_AUTHENTICATION_CLASSES': [ - 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.SessionAuthentication', ], - "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", "DEFAULT_FILTER_BACKENDS": [ "django_filters.rest_framework.DjangoFilterBackend" ], - 'DEFAULT_PARSER_CLASSES': [ - 'rest_framework.parsers.JSONParser', - ], - 'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'vrobbler.negotiation.IgnoreClientContentNegotiation', - "PAGE_SIZE": 100, + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", + "PAGE_SIZE": 200, } LOGIN_REDIRECT_URL = "/" diff --git a/vrobbler/urls.py b/vrobbler/urls.py index 798baaa..e3feb87 100644 --- a/vrobbler/urls.py +++ b/vrobbler/urls.py @@ -3,17 +3,46 @@ from django.conf import settings from django.conf.urls.static import static from django.contrib import admin from django.urls import include, path -from scrobbles import urls as scrobble_urls -from music import urls as music_urls -from videos import urls as video_urls +from rest_framework import routers + +from vrobbler.apps.books.api.views import AuthorViewSet, BookViewSet +from vrobbler.apps.music import urls as music_urls +from vrobbler.apps.music.api.views import ( + AlbumViewSet, + ArtistViewSet, + TrackViewSet, +) +from vrobbler.apps.profiles.api.views import UserProfileViewSet, UserViewSet +from vrobbler.apps.scrobbles import urls as scrobble_urls +from vrobbler.apps.scrobbles.api.views import ( + AudioScrobblerTSVImportViewSet, + KoReaderImportViewSet, + LastFmImportViewSet, + ScrobbleViewSet, +) +from vrobbler.apps.videos import urls as video_urls +from vrobbler.apps.videos.api.views import SeriesViewSet, VideoViewSet + +router = routers.DefaultRouter() +router.register(r'scrobbles', ScrobbleViewSet) +router.register(r'lastfm-imports', LastFmImportViewSet) +router.register(r'tsv-imports', AudioScrobblerTSVImportViewSet) +router.register(r'koreader-imports', KoReaderImportViewSet) +router.register(r'artist', ArtistViewSet) +router.register(r'album', AlbumViewSet) +router.register(r'tracks', TrackViewSet) +router.register(r'series', SeriesViewSet) +router.register(r'videos', VideoViewSet) +router.register(r'authors', AuthorViewSet) +router.register(r'books', BookViewSet) +router.register(r'users', UserViewSet) +router.register(r'user_profiles', UserProfileViewSet) urlpatterns = [ + path('api/v1/', include(router.urls)), + path('api/v1/auth', include("rest_framework.urls")), path("admin/", admin.site.urls), path("accounts/", include("allauth.urls")), - # path("api-auth/", include("rest_framework.urls")), - # path("movies/", include(movies, namespace="movies")), - # path("shows/", include(shows, namespace="shows")), - path("api/v1/scrobbles/", include(scrobble_urls, namespace="scrobbles")), path( 'manual/imdb/', scrobbles_views.ManualScrobbleView.as_view(),