Update importing to include some logging
This commit is contained in:
@ -13,7 +13,7 @@ class ScrobbleInline(admin.TabularInline):
|
|||||||
@admin.register(AudioScrobblerTSVImport)
|
@admin.register(AudioScrobblerTSVImport)
|
||||||
class AudioScrobblerTSVImportAdmin(admin.ModelAdmin):
|
class AudioScrobblerTSVImportAdmin(admin.ModelAdmin):
|
||||||
date_hierarchy = "created"
|
date_hierarchy = "created"
|
||||||
list_display = ("id", "tsv_file", "created")
|
list_display = ("uuid", "created", "process_count", "tsv_file")
|
||||||
ordering = ("-created",)
|
ordering = ("-created",)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.1.5 on 2023-02-03 22:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('scrobbles', '0013_audioscrobblertsvimport_processed_on'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='audioscrobblertsvimport',
|
||||||
|
name='process_log',
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 4.1.5 on 2023-02-03 23:36
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import scrobbles.models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('scrobbles', '0014_audioscrobblertsvimport_process_log'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='audioscrobblertsvimport',
|
||||||
|
name='uuid',
|
||||||
|
field=models.UUIDField(default=uuid.uuid4, editable=False),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='audioscrobblertsvimport',
|
||||||
|
name='tsv_file',
|
||||||
|
field=models.FileField(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
upload_to=scrobbles.models.AudioScrobblerTSVImport.get_path,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.1.5 on 2023-02-03 23:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('scrobbles', '0015_audioscrobblertsvimport_uuid_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='audioscrobblertsvimport',
|
||||||
|
name='process_count',
|
||||||
|
field=models.IntegerField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -18,18 +18,27 @@ BNULL = {"blank": True, "null": True}
|
|||||||
|
|
||||||
|
|
||||||
class AudioScrobblerTSVImport(TimeStampedModel):
|
class AudioScrobblerTSVImport(TimeStampedModel):
|
||||||
tsv_file = models.FileField(
|
def get_path(instance, filename):
|
||||||
upload_to="audioscrobbler-uploads/%Y/%m-%d/", **BNULL
|
extension = filename.split('.')[-1]
|
||||||
)
|
uuid = instance.uuid
|
||||||
|
return f'audioscrobbler-uploads/{uuid}.{extension}'
|
||||||
|
|
||||||
|
uuid = models.UUIDField(editable=False, default=uuid4)
|
||||||
|
tsv_file = models.FileField(upload_to=get_path, **BNULL)
|
||||||
processed_on = models.DateTimeField(**BNULL)
|
processed_on = models.DateTimeField(**BNULL)
|
||||||
|
process_log = models.TextField(**BNULL)
|
||||||
|
process_count = models.IntegerField(**BNULL)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Audioscrobbler TSV upload: {self.tsv_file.path}"
|
if self.tsv_file:
|
||||||
|
return f"Audioscrobbler TSV upload: {self.tsv_file.path}"
|
||||||
|
return f"Audioscrobbler TSV upload {self.id}"
|
||||||
|
|
||||||
def save(self, **kwargs):
|
def save(self, **kwargs):
|
||||||
"""On save, attempt to import the TSV file"""
|
"""On save, attempt to import the TSV file"""
|
||||||
|
super().save(**kwargs)
|
||||||
return super().save(**kwargs)
|
self.process()
|
||||||
|
return
|
||||||
|
|
||||||
def process(self, force=False):
|
def process(self, force=False):
|
||||||
from scrobbles.tsv import process_audioscrobbler_tsv_file
|
from scrobbles.tsv import process_audioscrobbler_tsv_file
|
||||||
@ -38,9 +47,21 @@ class AudioScrobblerTSVImport(TimeStampedModel):
|
|||||||
logger.info(f"{self} already processed on {self.processed_on}")
|
logger.info(f"{self} already processed on {self.processed_on}")
|
||||||
return
|
return
|
||||||
|
|
||||||
process_audioscrobbler_tsv_file(self.tsv_file.path)
|
scrobbles = process_audioscrobbler_tsv_file(self.tsv_file.path)
|
||||||
|
if scrobbles:
|
||||||
|
self.process_log = f"Created {len(scrobbles)} scrobbles"
|
||||||
|
for scrobble in scrobbles:
|
||||||
|
scrobble_str = f"{scrobble.id}\t{scrobble.timestamp}\t{scrobble.track.title}\t"
|
||||||
|
self.process_log += f"\n{scrobble_str}"
|
||||||
|
self.process_count = len(scrobbles)
|
||||||
|
else:
|
||||||
|
self.process_log = f"Created no new scrobbles"
|
||||||
|
self.process_count = 0
|
||||||
|
|
||||||
self.processed_on = timezone.now()
|
self.processed_on = timezone.now()
|
||||||
self.save(update_fields=['processed_on'])
|
self.save(
|
||||||
|
update_fields=['processed_on', 'process_count', 'process_log']
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChartRecord(TimeStampedModel):
|
class ChartRecord(TimeStampedModel):
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from scrobbles.models import Scrobble
|
from scrobbles.models import Scrobble, AudioScrobblerTSVImport
|
||||||
|
|
||||||
|
|
||||||
|
class AudioScrobblerTSVImportSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = AudioScrobblerTSVImport
|
||||||
|
fields = ('tsv_file',)
|
||||||
|
|
||||||
|
|
||||||
class ScrobbleSerializer(serializers.ModelSerializer):
|
class ScrobbleSerializer(serializers.ModelSerializer):
|
||||||
|
|||||||
@ -96,3 +96,4 @@ def process_audioscrobbler_tsv_file(file_path):
|
|||||||
f"Created {len(created)} scrobbles",
|
f"Created {len(created)} scrobbles",
|
||||||
extra={'created_scrobbles': created},
|
extra={'created_scrobbles': created},
|
||||||
)
|
)
|
||||||
|
return created
|
||||||
|
|||||||
@ -7,6 +7,11 @@ urlpatterns = [
|
|||||||
path('', views.scrobble_endpoint, name='api-list'),
|
path('', views.scrobble_endpoint, name='api-list'),
|
||||||
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(
|
||||||
|
'audioscrobbler-file-upload/',
|
||||||
|
views.import_audioscrobbler_file,
|
||||||
|
name='audioscrobbler-file-upload',
|
||||||
|
),
|
||||||
path('jellyfin/', views.jellyfin_websocket, name='jellyfin-websocket'),
|
path('jellyfin/', views.jellyfin_websocket, name='jellyfin-websocket'),
|
||||||
path('mopidy/', views.mopidy_websocket, name='mopidy-websocket'),
|
path('mopidy/', views.mopidy_websocket, name='mopidy-websocket'),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -11,7 +11,12 @@ from django.views.decorators.csrf import csrf_exempt
|
|||||||
from django.views.generic import FormView
|
from django.views.generic import FormView
|
||||||
from django.views.generic.list import ListView
|
from django.views.generic.list import ListView
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import api_view, permission_classes
|
from rest_framework.decorators import (
|
||||||
|
api_view,
|
||||||
|
parser_classes,
|
||||||
|
permission_classes,
|
||||||
|
)
|
||||||
|
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.constants import (
|
from scrobbles.constants import (
|
||||||
@ -29,7 +34,10 @@ from scrobbles.scrobblers import (
|
|||||||
mopidy_scrobble_podcast,
|
mopidy_scrobble_podcast,
|
||||||
mopidy_scrobble_track,
|
mopidy_scrobble_track,
|
||||||
)
|
)
|
||||||
from scrobbles.serializers import ScrobbleSerializer
|
from scrobbles.serializers import (
|
||||||
|
AudioScrobblerTSVImportSerializer,
|
||||||
|
ScrobbleSerializer,
|
||||||
|
)
|
||||||
from scrobbles.thesportsdb import lookup_event_from_thesportsdb
|
from scrobbles.thesportsdb import lookup_event_from_thesportsdb
|
||||||
|
|
||||||
from vrobbler.apps.music.aggregators import (
|
from vrobbler.apps.music.aggregators import (
|
||||||
@ -187,6 +195,29 @@ def mopidy_websocket(request):
|
|||||||
return Response({'scrobble_id': scrobble.id}, status=status.HTTP_200_OK)
|
return Response({'scrobble_id': scrobble.id}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
@permission_classes([IsAuthenticated])
|
||||||
|
@api_view(['POST'])
|
||||||
|
@parser_classes([MultiPartParser])
|
||||||
|
def import_audioscrobbler_file(request):
|
||||||
|
"""Takes a TSV file in the Audioscrobbler format, saves it and processes the
|
||||||
|
scrobbles.
|
||||||
|
"""
|
||||||
|
scrobbles_created = []
|
||||||
|
# tsv_file = request.FILES[0]
|
||||||
|
|
||||||
|
file_serializer = AudioScrobblerTSVImportSerializer(data=request.data)
|
||||||
|
if file_serializer.is_valid():
|
||||||
|
import_file = file_serializer.save()
|
||||||
|
return Response(
|
||||||
|
{'scrobble_ids': scrobbles_created}, status=status.HTTP_200_OK
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return Response(
|
||||||
|
file_serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
@api_view(['GET'])
|
@api_view(['GET'])
|
||||||
|
|||||||
@ -179,7 +179,11 @@
|
|||||||
{% for scrobble in object_list %}
|
{% for scrobble in object_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{scrobble.timestamp|naturaltime}}</td>
|
<td>{{scrobble.timestamp|naturaltime}}</td>
|
||||||
|
{% if scrobble.track.album.cover_image %}
|
||||||
<td><img src="{{scrobble.track.album.cover_image.url}}" width=50 height=50 style="border:1px solid black;" /></td>
|
<td><img src="{{scrobble.track.album.cover_image.url}}" width=50 height=50 style="border:1px solid black;" /></td>
|
||||||
|
{% else %}
|
||||||
|
<td>{{scrobble.track.album.name}}</td>
|
||||||
|
{% endif %}
|
||||||
<td>{{scrobble.track.title}}</td>
|
<td>{{scrobble.track.title}}</td>
|
||||||
<td>{{scrobble.track.artist.name}}</td>
|
<td>{{scrobble.track.artist.name}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user