Compare commits

..

4 Commits

Author SHA1 Message Date
927d0be1b8 Bump version to 0.10.1 2023-02-23 10:14:41 -05:00
f6b9245b8b Add looking tracks without MB IDs by looking them up 2023-02-23 10:07:29 -05:00
39e035b460 Clean up URLs and templates 2023-02-21 00:17:31 -05:00
cf9da39967 Update API to be more complete 2023-02-20 17:08:54 -05:00
28 changed files with 376 additions and 91 deletions

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "vrobbler" name = "vrobbler"
version = "0.10.0" version = "0.10.1"
description = "" description = ""
authors = ["Colin Powell <colin@unbl.ink>"] authors = ["Colin Powell <colin@unbl.ink>"]

View File

@ -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__"

View File

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

View File

@ -8,7 +8,8 @@ from django.urls import reverse
from django_extensions.db.models import TimeStampedModel from django_extensions.db.models import TimeStampedModel
from scrobbles.mixins import ScrobblableMixin 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__) logger = logging.getLogger(__name__)
User = get_user_model() User = get_user_model()
@ -66,3 +67,7 @@ class Book(ScrobblableMixin):
logger.warn(f"{self} has no pages, no completion percentage") logger.warn(f"{self} has no pages, no completion percentage")
return 0 return 0
return int(self.pages * (self.COMPLETION_PERCENT / 100)) 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)

View File

View File

@ -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__"

View File

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

View File

@ -0,0 +1 @@
#!/usr/bin/env python3

View File

@ -4,6 +4,7 @@ import re
from scrobbles.musicbrainz import ( from scrobbles.musicbrainz import (
lookup_album_dict_from_mb, lookup_album_dict_from_mb,
lookup_artist_from_mb, lookup_artist_from_mb,
lookup_track_from_mb,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -93,7 +94,11 @@ def get_or_create_track(
title=title, artist=artist, album=album title=title, artist=artist, album=album
).first() ).first()
# TODO Can we look up mbid for tracks? if not mbid:
mbid = lookup_track_from_mb(
title, artist.musicbrainz_id, album.musicbrainz_id
)['id']
if not track: if not track:
track = Track.objects.create( track = Track.objects.create(
title=title, title=title,

View File

@ -0,0 +1 @@
#!/usr/bin/env python3

View File

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

View File

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

View File

View File

@ -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__"

View File

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

View File

@ -90,16 +90,18 @@ def lookup_artist_from_mb(artist_name: str) -> str:
return top_result return top_result
def lookup_track_from_mb(artist_name: str) -> str: def lookup_track_from_mb(
track_name: str, artist_mbid: str, album_mbid: str
) -> str:
musicbrainzngs.set_useragent('vrobbler', '0.3.0') musicbrainzngs.set_useragent('vrobbler', '0.3.0')
top_result = musicbrainzngs.search_recordings(artist=artist_name)[ top_result = musicbrainzngs.search_recordings(
'artist-list' query=track_name, artist=artist_mbid, release=album_mbid
][0] )['recording-list'][0]
score = int(top_result.get('ext:score')) score = int(top_result.get('ext:score'))
if score < 85: if score < 85:
logger.debug( logger.debug(
"Artist lookup score below 85 threshold", "Track lookup score below 85 threshold",
extra={"result": top_result}, extra={"result": top_result},
) )
return "" return ""

View File

@ -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__"

View File

@ -4,7 +4,21 @@ from scrobbles import views
app_name = 'scrobbles' app_name = 'scrobbles'
urlpatterns = [ urlpatterns = [
path('', views.scrobble_endpoint, name='api-list'), path(
'manual/imdb/',
views.ManualScrobbleView.as_view(),
name='imdb-manual-scrobble',
),
path(
'manual/audioscrobbler/',
views.AudioScrobblerImportCreateView.as_view(),
name='audioscrobbler-file-upload',
),
path(
'manual/koreader/',
views.KoReaderImportCreateView.as_view(),
name='koreader-file-upload',
),
path('finish/<slug:uuid>', views.scrobble_finish, name='finish'), path('finish/<slug:uuid>', views.scrobble_finish, name='finish'),
path('cancel/<slug:uuid>', views.scrobble_cancel, name='cancel'), path('cancel/<slug:uuid>', views.scrobble_cancel, name='cancel'),
path( path(

View File

@ -1,10 +1,14 @@
import logging import logging
from urllib.parse import unquote from urllib.parse import unquote
from django.contrib.auth import get_user_model
from dateutil.parser import ParserError, parse from dateutil.parser import ParserError, parse
from django.conf import settings from django.conf import settings
from django.db import models
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
User = get_user_model()
def convert_to_seconds(run_time: str) -> int: 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): 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.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']) 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)

View File

@ -4,6 +4,7 @@ from datetime import datetime
import pytz import pytz
from django.conf import settings from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models.fields import timezone from django.db.models.fields import timezone
from django.http import FileResponse, HttpResponseRedirect, JsonResponse from django.http import FileResponse, HttpResponseRedirect, JsonResponse
@ -28,6 +29,7 @@ from rest_framework.decorators import (
from rest_framework.parsers import MultiPartParser from rest_framework.parsers import MultiPartParser
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response from rest_framework.response import Response
from scrobbles.api import serializers
from scrobbles.constants import ( from scrobbles.constants import (
JELLYFIN_AUDIO_ITEM_TYPES, JELLYFIN_AUDIO_ITEM_TYPES,
JELLYFIN_VIDEO_ITEM_TYPES, JELLYFIN_VIDEO_ITEM_TYPES,
@ -49,10 +51,6 @@ from scrobbles.scrobblers import (
mopidy_scrobble_podcast, mopidy_scrobble_podcast,
mopidy_scrobble_track, mopidy_scrobble_track,
) )
from scrobbles.serializers import (
AudioScrobblerTSVImportSerializer,
ScrobbleSerializer,
)
from scrobbles.tasks import ( from scrobbles.tasks import (
process_koreader_import, process_koreader_import,
process_lastfm_import, process_lastfm_import,
@ -215,15 +213,6 @@ def lastfm_import(request):
return HttpResponseRedirect(success_url) 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 @csrf_exempt
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@api_view(['POST']) @api_view(['POST'])
@ -293,7 +282,9 @@ def import_audioscrobbler_file(request):
scrobbles_created = [] scrobbles_created = []
# tsv_file = request.FILES[0] # tsv_file = request.FILES[0]
file_serializer = AudioScrobblerTSVImportSerializer(data=request.data) file_serializer = serializers.AudioScrobblerTSVImportSerializer(
data=request.data
)
if file_serializer.is_valid(): if file_serializer.is_valid():
import_file = file_serializer.save() import_file = file_serializer.save()
return Response( return Response(
@ -305,39 +296,48 @@ def import_audioscrobbler_file(request):
) )
@csrf_exempt
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@api_view(['GET']) @api_view(['GET'])
def scrobble_finish(request, uuid): def scrobble_finish(request, uuid):
user = request.user user = request.user
success_url = reverse_lazy('vrobbler-home')
if not user.is_authenticated: if not user.is_authenticated:
return Response({}, status=status.HTTP_403_FORBIDDEN) return HttpResponseRedirect(success_url)
scrobble = Scrobble.objects.filter(user=user, uuid=uuid).first() scrobble = Scrobble.objects.filter(user=user, uuid=uuid).first()
if not scrobble: if scrobble:
return Response({}, status=status.HTTP_404_NOT_FOUND) scrobble.stop(force_finish=True)
scrobble.stop(force_finish=True) messages.add_message(
return Response( request,
{'id': scrobble.id, 'status': scrobble.status}, messages.SUCCESS,
status=status.HTTP_200_OK, f"Scrobble of {scrobble.media_obj} finished.",
) )
else:
messages.add_message(request, messages.ERROR, "Scrobble not found.")
return HttpResponseRedirect(success_url)
@csrf_exempt
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@api_view(['GET']) @api_view(['GET'])
def scrobble_cancel(request, uuid): def scrobble_cancel(request, uuid):
user = request.user user = request.user
success_url = reverse_lazy('vrobbler-home')
if not user.is_authenticated: if not user.is_authenticated:
return Response({}, status=status.HTTP_403_FORBIDDEN) return HttpResponseRedirect(success_url)
scrobble = Scrobble.objects.filter(user=user, uuid=uuid).first() scrobble = Scrobble.objects.filter(user=user, uuid=uuid).first()
if not scrobble: if scrobble:
return Response({}, status=status.HTTP_404_NOT_FOUND) scrobble.cancel()
scrobble.cancel() messages.add_message(
return Response( request,
{'id': scrobble.id, 'status': 'cancelled'}, status=status.HTTP_200_OK messages.SUCCESS,
) f"Scrobble of {scrobble.media_obj} cancelled.",
)
else:
messages.add_message(request, messages.ERROR, "Scrobble not found.")
return HttpResponseRedirect(success_url)
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])

View File

View File

@ -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__"

View File

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

View File

@ -140,6 +140,8 @@ TEMPLATES = [
}, },
] ]
MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage"
WSGI_APPLICATION = "vrobbler.wsgi.application" WSGI_APPLICATION = "vrobbler.wsgi.application"
DATABASES = { DATABASES = {
@ -174,19 +176,14 @@ AUTHENTICATION_BACKENDS = [
REST_FRAMEWORK = { REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.AllowAny",), "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.AllowAny",),
'DEFAULT_AUTHENTICATION_CLASSES': [ 'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.SessionAuthentication',
], ],
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"DEFAULT_FILTER_BACKENDS": [ "DEFAULT_FILTER_BACKENDS": [
"django_filters.rest_framework.DjangoFilterBackend" "django_filters.rest_framework.DjangoFilterBackend"
], ],
'DEFAULT_PARSER_CLASSES': [ "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
'rest_framework.parsers.JSONParser', "PAGE_SIZE": 200,
],
'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'vrobbler.negotiation.IgnoreClientContentNegotiation',
"PAGE_SIZE": 100,
} }
LOGIN_REDIRECT_URL = "/" LOGIN_REDIRECT_URL = "/"

View File

@ -177,7 +177,7 @@
</button> </button>
{% if user.is_authenticated %} {% if user.is_authenticated %}
<form id="scrobble-form" action="{% url 'imdb-manual-scrobble' %}" method="post"> <form id="scrobble-form" action="{% url 'scrobbles:imdb-manual-scrobble' %}" method="post">
{% csrf_token %} {% csrf_token %}
{{ imdb_form }} {{ imdb_form }}
</form> </form>
@ -197,6 +197,13 @@
<div class="row"> <div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse"> <nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="position-sticky pt-3"> <div class="position-sticky pt-3">
{% if messages %}
<ul style="padding-right:10px;">
{% for message in messages %}
<li {% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% if now_playing_list and user.is_authenticated %} {% if now_playing_list and user.is_authenticated %}
<ul style="padding-right:10px;"> <ul style="padding-right:10px;">
<b>Now playing</b> <b>Now playing</b>
@ -219,8 +226,6 @@
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
<ul class="nav flex-column"> <ul class="nav flex-column">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link active" aria-current="page" href="/"> <a class="nav-link active" aria-current="page" href="/">
@ -271,6 +276,7 @@
{% endblock %} {% endblock %}
</div> </div>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js" integrity="sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js" integrity="sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha" crossorigin="anonymous"></script>
<script><!-- comment -------------------------------------------------> <script><!-- comment ------------------------------------------------->
/* globals Chart:false, feather:false */ /* globals Chart:false, feather:false */

View File

@ -316,7 +316,7 @@
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
</button> </button>
</div> </div>
<form action="{% url 'audioscrobbler-file-upload' %}" method="post" enctype="multipart/form-data"> <form action="{% url 'scrobbles:audioscrobbler-file-upload' %}" method="post" enctype="multipart/form-data">
<div class="modal-body"> <div class="modal-body">
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
@ -328,7 +328,7 @@
<button type="submit" class="btn btn-primary">Import</button> <button type="submit" class="btn btn-primary">Import</button>
</div> </div>
</form> </form>
<form action="{% url 'koreader-file-upload' %}" method="post" enctype="multipart/form-data"> <form action="{% url 'scrobbles:koreader-file-upload' %}" method="post" enctype="multipart/form-data">
<div class="modal-body"> <div class="modal-body">
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">

View File

@ -1,13 +1,14 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<main class="col-md-4 ms-sm-auto col-lg-10 px-md-4"> <main class="col-md-4 ms-sm-auto col-lg-10 px-md-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <div
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Manual scrobble</h1> <h1 class="h2">Manual scrobble</h1>
<form action="{% url 'audioscrobbler-file-upload' %}" method="post"> <form action="{% url 'scrobbles:audioscrobbler-file-upload' %}" method="post">
{% csrf_token %} {% csrf_token %}
{{ form }} {{ form }}
<input type="submit" value="Submit"> <input type="submit" value="Submit">
</form> </form>
</div> </div>
</main> </main>
{% endblock %} {% endblock %}

View File

@ -3,34 +3,49 @@ from django.conf import settings
from django.conf.urls.static import static from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from scrobbles import urls as scrobble_urls from rest_framework import routers
from music import urls as music_urls
from videos import urls as video_urls 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 = [ urlpatterns = [
path('api/v1/', include(router.urls)),
path('api/v1/auth', include("rest_framework.urls")),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("accounts/", include("allauth.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(),
name='imdb-manual-scrobble',
),
path(
'manual/audioscrobbler/',
scrobbles_views.AudioScrobblerImportCreateView.as_view(),
name='audioscrobbler-file-upload',
),
path(
'manual/koreader/',
scrobbles_views.KoReaderImportCreateView.as_view(),
name='koreader-file-upload',
),
path("", include(music_urls, namespace="music")), path("", include(music_urls, namespace="music")),
path("", include(video_urls, namespace="videos")), path("", include(video_urls, namespace="videos")),
path("", include(scrobble_urls, namespace="scrobbles")),
path( path(
"", scrobbles_views.RecentScrobbleList.as_view(), name="vrobbler-home" "", scrobbles_views.RecentScrobbleList.as_view(), name="vrobbler-home"
), ),