kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
wip: add v2 upload endpoint
rodzic
67b43e73d1
commit
6d00aa28c0
|
@ -1585,3 +1585,7 @@ class UploadGroup(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def upload_url(self):
|
||||
return f"{settings.FUNKWHALE_URL}/api/v2/upload-groups/{self.guid}/uploads"
|
||||
|
|
|
@ -896,8 +896,99 @@ class UploadGroupSerializer(serializers.ModelSerializer):
|
|||
fields = ["guid", "name", "createdAt", "uploadUrl"]
|
||||
|
||||
name = serializers.CharField(required=False)
|
||||
uploadUrl = serializers.SerializerMethodField(read_only=True)
|
||||
uploadUrl = serializers.URLField(read_only=True, source="upload_url")
|
||||
createdAt = serializers.DateTimeField(read_only=True, source="created_at")
|
||||
|
||||
def get_uploadUrl(self, value):
|
||||
return f"{settings.FUNKWHALE_URL}/api/v2/upload-groups/{value.guid}/uploads"
|
||||
|
||||
class UploadGroupUploadMetadataReleaseSerializer(serializers.Serializer):
|
||||
title = serializers.CharField()
|
||||
artist = serializers.CharField()
|
||||
mbid = serializers.UUIDField(required=False)
|
||||
|
||||
|
||||
class UploadGroupUploadMetadataArtistSerializer(serializers.Serializer):
|
||||
name = serializers.CharField()
|
||||
mbid = serializers.UUIDField(required=False)
|
||||
|
||||
|
||||
class UploadGroupUploadMetadataSerializer(serializers.Serializer):
|
||||
title = serializers.CharField()
|
||||
mbid = serializers.UUIDField(required=False)
|
||||
tags = serializers.ListField(child=serializers.CharField(), required=False)
|
||||
position = serializers.IntegerField(required=False)
|
||||
entryNumber = serializers.IntegerField(required=False)
|
||||
releaseDate = serializers.DateField(required=False)
|
||||
license = serializers.URLField(required=False)
|
||||
release = UploadGroupUploadMetadataReleaseSerializer(required=False)
|
||||
artist = UploadGroupUploadMetadataArtistSerializer(required=False)
|
||||
|
||||
|
||||
class TargetSerializer(serializers.Serializer):
|
||||
library = serializers.UUIDField(required=False)
|
||||
collections = serializers.ListField(child=serializers.UUIDField(), required=False)
|
||||
channels = serializers.ListField(child=serializers.UUIDField(), required=False)
|
||||
|
||||
def validate(self, data):
|
||||
# At the moment we allow to set exactly one target, it can be either a library or a channel.
|
||||
# The structure already allows setting multiple targets in the future, however this is disabled for now.
|
||||
if "channels" in data and "library" in data:
|
||||
raise serializers.ValidationError
|
||||
if "channels" not in data and "library" not in data:
|
||||
raise serializers.ValidationError
|
||||
if "collections" in data:
|
||||
raise serializers.ValidationError("Not yet implemented")
|
||||
try:
|
||||
if len(data.channels) > 1:
|
||||
raise serializers.ValidationError
|
||||
except AttributeError:
|
||||
pass
|
||||
return data
|
||||
|
||||
|
||||
class UploadGroupUploadSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.Upload
|
||||
fields = [
|
||||
"audioFile",
|
||||
"target",
|
||||
"metadata",
|
||||
] # , "cover"] TODO we need to process the cover
|
||||
|
||||
metadata = serializers.JSONField(source="import_metadata")
|
||||
target = serializers.JSONField()
|
||||
audioFile = serializers.FileField(source="audio_file")
|
||||
# cover = serializers.FileField(required=False)
|
||||
|
||||
def validate_target(self, value):
|
||||
serializer = TargetSerializer(data=value)
|
||||
if serializer.is_valid():
|
||||
return serializer.validated_data
|
||||
else:
|
||||
print(serializer.errors)
|
||||
raise serializers.ValidationError
|
||||
|
||||
def validate_metadata(self, value):
|
||||
serializer = UploadGroupUploadMetadataSerializer(data=value)
|
||||
if serializer.is_valid():
|
||||
return serializer.validated_data
|
||||
else:
|
||||
print(serializer.errors)
|
||||
raise serializers.ValidationError
|
||||
|
||||
def create(self, validated_data):
|
||||
library = models.Library.objects.get(uuid=validated_data["target"]["library"])
|
||||
del validated_data["target"]
|
||||
return models.Upload.objects.create(
|
||||
library=library, source="upload://test", **validated_data
|
||||
)
|
||||
|
||||
|
||||
class BaseUploadSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = models.Upload
|
||||
fields = ["guid", "createdDate", "uploadGroup", "status"]
|
||||
|
||||
guid = serializers.UUIDField(source="uuid")
|
||||
createdDate = serializers.DateTimeField(source="creation_date")
|
||||
uploadGroup = serializers.UUIDField(source="upload_group.guid")
|
||||
status = serializers.CharField(source="import_status")
|
||||
|
|
|
@ -16,6 +16,7 @@ from rest_framework import mixins, renderers
|
|||
from rest_framework import settings as rest_settings
|
||||
from rest_framework import views, viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.parsers import FormParser, MultiPartParser
|
||||
from rest_framework.response import Response
|
||||
|
||||
from funkwhale_api.common import decorators as common_decorators
|
||||
|
@ -1009,3 +1010,26 @@ class UploadGroupViewSet(viewsets.ModelViewSet):
|
|||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(owner=self.request.user.actor)
|
||||
|
||||
@action(
|
||||
detail=True,
|
||||
methods=["post"],
|
||||
parser_classes=(MultiPartParser, FormParser),
|
||||
serializer_class=serializers.UploadGroupUploadSerializer,
|
||||
)
|
||||
def uploads(self, request, pk=None):
|
||||
print(request.data)
|
||||
serializer = self.get_serializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
upload_group = models.UploadGroup.objects.get(guid=pk)
|
||||
if upload_group.owner == request.user.actor:
|
||||
upload = serializer.save(upload_group=upload_group)
|
||||
common_utils.on_commit(tasks.process_upload.delay, upload_id=upload.pk)
|
||||
response = serializers.BaseUploadSerializer(upload).data
|
||||
return Response(response, status=200)
|
||||
else:
|
||||
return Response("You don't own this Upload Group", status=403)
|
||||
else:
|
||||
print(serializer.errors)
|
||||
|
||||
return Response("Fehler", status=202)
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
import pytest
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
def test_can_resolve_upload_urls():
|
||||
url = reverse("api:v2:upload-groups-list")
|
||||
assert url == "/api/v2/upload-groups"
|
||||
@pytest.mark.parametrize(
|
||||
"input,args,expected_url",
|
||||
[
|
||||
("api:v2:upload-groups-list", None, "/api/v2/upload-groups"),
|
||||
(
|
||||
"api:v2:upload-groups-uploads",
|
||||
["1234-1234-1234"],
|
||||
"/api/v2/upload-groups/1234-1234-1234/uploads",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_can_resolve_upload_urls(input, args, expected_url):
|
||||
url = reverse(input, args=args)
|
||||
assert url == expected_url
|
||||
|
|
|
@ -1697,3 +1697,58 @@ def test_can_create_upload_group_with_name(logged_in_api_client):
|
|||
assert "https://test.federation/api/v2/upload-groups/" in response.data.get(
|
||||
"uploadUrl"
|
||||
)
|
||||
|
||||
|
||||
def test_user_can_create_upload_v2(logged_in_api_client, factories, mocker, audio_file):
|
||||
library = factories["music.Library"](actor__user=logged_in_api_client.user)
|
||||
logged_in_api_client.user.create_actor()
|
||||
|
||||
upload_group = factories["music.UploadGroup"](owner=logged_in_api_client.user.actor)
|
||||
upload_url = upload_group.upload_url
|
||||
|
||||
m = mocker.patch("funkwhale_api.common.utils.on_commit")
|
||||
|
||||
response = logged_in_api_client.post(
|
||||
upload_url,
|
||||
{
|
||||
"audioFile": audio_file,
|
||||
"metadata": '{"title": "foo"}',
|
||||
"target": f'{{"library": "{ library.uuid }"}}',
|
||||
},
|
||||
)
|
||||
|
||||
print(response.data)
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
upload = library.uploads.latest("id")
|
||||
|
||||
audio_file.seek(0)
|
||||
assert upload.audio_file.read() == audio_file.read()
|
||||
assert upload.source == "upload://test"
|
||||
assert upload.import_status == "pending"
|
||||
assert upload.import_metadata == {"title": "foo"}
|
||||
assert upload.track is None
|
||||
assert upload.upload_group == upload_group
|
||||
m.assert_called_once_with(tasks.process_upload.delay, upload_id=upload.pk)
|
||||
|
||||
|
||||
def test_user_cannot_create_upload_for_foreign_group(
|
||||
logged_in_api_client, factories, mocker, audio_file
|
||||
):
|
||||
library = factories["music.Library"](actor__user=logged_in_api_client.user)
|
||||
logged_in_api_client.user.create_actor()
|
||||
|
||||
upload_group = factories["music.UploadGroup"]()
|
||||
upload_url = upload_group.upload_url
|
||||
|
||||
response = logged_in_api_client.post(
|
||||
upload_url,
|
||||
{
|
||||
"audioFile": audio_file,
|
||||
"metadata": '{"title": "foo"}',
|
||||
"target": f'{{"library": "{ library.uuid }"}}',
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
|
Ładowanie…
Reference in New Issue