diff --git a/api/funkwhale_api/favorites/serializers.py b/api/funkwhale_api/favorites/serializers.py
index 66e10a1b4..dd28dcd07 100644
--- a/api/funkwhale_api/favorites/serializers.py
+++ b/api/funkwhale_api/favorites/serializers.py
@@ -1,6 +1,7 @@
from rest_framework import serializers
from funkwhale_api.activity import serializers as activity_serializers
+from funkwhale_api.federation import serializers as federation_serializers
from funkwhale_api.music.serializers import TrackActivitySerializer, TrackSerializer
from funkwhale_api.users.serializers import UserActivitySerializer, UserBasicSerializer
@@ -27,10 +28,17 @@ class TrackFavoriteActivitySerializer(activity_serializers.ModelSerializer):
class UserTrackFavoriteSerializer(serializers.ModelSerializer):
track = TrackSerializer(read_only=True)
user = UserBasicSerializer(read_only=True)
+ actor = serializers.SerializerMethodField()
class Meta:
model = models.TrackFavorite
- fields = ("id", "user", "track", "creation_date")
+ fields = ("id", "user", "track", "creation_date", "actor")
+ actor = serializers.SerializerMethodField()
+
+ def get_actor(self, obj):
+ actor = obj.user.actor
+ if actor:
+ return federation_serializers.APIActorSerializer(actor).data
class UserTrackFavoriteWriteSerializer(serializers.ModelSerializer):
diff --git a/api/funkwhale_api/favorites/views.py b/api/funkwhale_api/favorites/views.py
index dce285d85..7d1991bc6 100644
--- a/api/funkwhale_api/favorites/views.py
+++ b/api/funkwhale_api/favorites/views.py
@@ -22,7 +22,7 @@ class TrackFavoriteViewSet(
filterset_class = filters.TrackFavoriteFilter
serializer_class = serializers.UserTrackFavoriteSerializer
- queryset = models.TrackFavorite.objects.all().select_related("user")
+ queryset = models.TrackFavorite.objects.all().select_related("user__actor")
permission_classes = [
oauth_permissions.ScopePermission,
permissions.OwnerPermission,
@@ -54,7 +54,7 @@ class TrackFavoriteViewSet(
)
tracks = Track.objects.with_playable_uploads(
music_utils.get_actor_from_request(self.request)
- ).select_related("artist", "album__artist")
+ ).select_related("artist", "album__artist", "attributed_to")
queryset = queryset.prefetch_related(Prefetch("track", queryset=tracks))
return queryset
diff --git a/api/funkwhale_api/history/serializers.py b/api/funkwhale_api/history/serializers.py
index 2254aee8c..c894ec59a 100644
--- a/api/funkwhale_api/history/serializers.py
+++ b/api/funkwhale_api/history/serializers.py
@@ -1,6 +1,7 @@
from rest_framework import serializers
from funkwhale_api.activity import serializers as activity_serializers
+from funkwhale_api.federation import serializers as federation_serializers
from funkwhale_api.music.serializers import TrackActivitySerializer, TrackSerializer
from funkwhale_api.users.serializers import UserActivitySerializer, UserBasicSerializer
@@ -27,16 +28,22 @@ class ListeningActivitySerializer(activity_serializers.ModelSerializer):
class ListeningSerializer(serializers.ModelSerializer):
track = TrackSerializer(read_only=True)
user = UserBasicSerializer(read_only=True)
+ actor = serializers.SerializerMethodField()
class Meta:
model = models.Listening
- fields = ("id", "user", "track", "creation_date")
+ fields = ("id", "user", "track", "creation_date", "actor")
def create(self, validated_data):
validated_data["user"] = self.context["user"]
return super().create(validated_data)
+ def get_actor(self, obj):
+ actor = obj.user.actor
+ if actor:
+ return federation_serializers.APIActorSerializer(actor).data
+
class ListeningWriteSerializer(serializers.ModelSerializer):
class Meta:
diff --git a/api/funkwhale_api/history/views.py b/api/funkwhale_api/history/views.py
index 30219629a..6cdbc8a80 100644
--- a/api/funkwhale_api/history/views.py
+++ b/api/funkwhale_api/history/views.py
@@ -19,7 +19,7 @@ class ListeningViewSet(
):
serializer_class = serializers.ListeningSerializer
- queryset = models.Listening.objects.all().select_related("user")
+ queryset = models.Listening.objects.all().select_related("user__actor")
permission_classes = [
oauth_permissions.ScopePermission,
@@ -47,7 +47,7 @@ class ListeningViewSet(
)
tracks = Track.objects.with_playable_uploads(
music_utils.get_actor_from_request(self.request)
- ).select_related("artist", "album__artist")
+ ).select_related("artist", "album__artist", "attributed_to")
return queryset.prefetch_related(Prefetch("track", queryset=tracks))
def get_serializer_context(self):
diff --git a/api/funkwhale_api/instance/nodeinfo.py b/api/funkwhale_api/instance/nodeinfo.py
index 178a8c1ab..ecdca9e40 100644
--- a/api/funkwhale_api/instance/nodeinfo.py
+++ b/api/funkwhale_api/instance/nodeinfo.py
@@ -3,6 +3,7 @@ import memoize.djangocache
import funkwhale_api
from funkwhale_api.common import preferences
from funkwhale_api.federation import actors, models as federation_models
+from funkwhale_api.moderation import models as moderation_models
from funkwhale_api.music import utils as music_utils
from . import stats
@@ -15,6 +16,9 @@ def get():
share_stats = preferences.get("instance__nodeinfo_stats_enabled")
allow_list_enabled = preferences.get("moderation__allow_list_enabled")
allow_list_public = preferences.get("moderation__allow_list_public")
+ unauthenticated_report_types = preferences.get(
+ "moderation__unauthenticated_report_types"
+ )
if allow_list_enabled and allow_list_public:
allowed_domains = list(
federation_models.Domain.objects.filter(allowed=True)
@@ -47,6 +51,10 @@ def get():
},
"supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS,
"allowList": {"enabled": allow_list_enabled, "domains": allowed_domains},
+ "reportTypes": [
+ {"type": t, "label": l, "anonymous": t in unauthenticated_report_types}
+ for t, l in moderation_models.REPORT_TYPES
+ ],
},
}
if share_stats:
diff --git a/api/funkwhale_api/moderation/models.py b/api/funkwhale_api/moderation/models.py
index 5a4081b7b..c2b91760d 100644
--- a/api/funkwhale_api/moderation/models.py
+++ b/api/funkwhale_api/moderation/models.py
@@ -115,7 +115,7 @@ REPORT_TYPES = [
class Report(federation_models.FederationMixin):
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
creation_date = models.DateTimeField(default=timezone.now)
- summary = models.TextField(null=True, max_length=50000)
+ summary = models.TextField(null=True, blank=True, max_length=50000)
handled_date = models.DateTimeField(null=True)
is_handled = models.BooleanField(default=False)
type = models.CharField(max_length=40, choices=REPORT_TYPES)
diff --git a/api/funkwhale_api/playlists/serializers.py b/api/funkwhale_api/playlists/serializers.py
index ccdf82f4b..dc61950dd 100644
--- a/api/funkwhale_api/playlists/serializers.py
+++ b/api/funkwhale_api/playlists/serializers.py
@@ -2,6 +2,7 @@ from django.db import transaction
from rest_framework import serializers
from funkwhale_api.common import preferences
+from funkwhale_api.federation import serializers as federation_serializers
from funkwhale_api.music.models import Track
from funkwhale_api.music.serializers import TrackSerializer
from funkwhale_api.users.serializers import UserBasicSerializer
@@ -79,6 +80,7 @@ class PlaylistSerializer(serializers.ModelSerializer):
album_covers = serializers.SerializerMethodField(read_only=True)
user = UserBasicSerializer(read_only=True)
is_playable = serializers.SerializerMethodField()
+ actor = serializers.SerializerMethodField()
class Meta:
model = models.Playlist
@@ -93,9 +95,15 @@ class PlaylistSerializer(serializers.ModelSerializer):
"album_covers",
"duration",
"is_playable",
+ "actor",
)
read_only_fields = ["id", "modification_date", "creation_date"]
+ def get_actor(self, obj):
+ actor = obj.user.actor
+ if actor:
+ return federation_serializers.APIActorSerializer(actor).data
+
def get_is_playable(self, obj):
try:
return bool(obj.playable_plts)
diff --git a/api/funkwhale_api/playlists/views.py b/api/funkwhale_api/playlists/views.py
index 861dc8175..6f9ea23ce 100644
--- a/api/funkwhale_api/playlists/views.py
+++ b/api/funkwhale_api/playlists/views.py
@@ -23,7 +23,7 @@ class PlaylistViewSet(
serializer_class = serializers.PlaylistSerializer
queryset = (
models.Playlist.objects.all()
- .select_related("user")
+ .select_related("user__actor")
.annotate(tracks_count=Count("playlist_tracks"))
.with_covers()
.with_duration()
diff --git a/api/tests/favorites/test_favorites.py b/api/tests/favorites/test_favorites.py
index 190c79184..b81006386 100644
--- a/api/tests/favorites/test_favorites.py
+++ b/api/tests/favorites/test_favorites.py
@@ -4,8 +4,7 @@ import pytest
from django.urls import reverse
from funkwhale_api.favorites.models import TrackFavorite
-from funkwhale_api.music import serializers as music_serializers
-from funkwhale_api.users import serializers as users_serializers
+from funkwhale_api.favorites import serializers
def test_user_can_add_favorite(factories):
@@ -20,22 +19,15 @@ def test_user_can_add_favorite(factories):
def test_user_can_get_his_favorites(
api_request, factories, logged_in_api_client, client
):
- r = api_request.get("/")
+ request = api_request.get("/")
favorite = factories["favorites.TrackFavorite"](user=logged_in_api_client.user)
factories["favorites.TrackFavorite"]()
url = reverse("api:v1:favorites:tracks-list")
response = logged_in_api_client.get(url, {"user": logged_in_api_client.user.pk})
expected = [
- {
- "user": users_serializers.UserBasicSerializer(
- favorite.user, context={"request": r}
- ).data,
- "track": music_serializers.TrackSerializer(
- favorite.track, context={"request": r}
- ).data,
- "id": favorite.id,
- "creation_date": favorite.creation_date.isoformat().replace("+00:00", "Z"),
- }
+ serializers.UserTrackFavoriteSerializer(
+ favorite, context={"request": request}
+ ).data
]
assert response.status_code == 200
assert response.data["results"] == expected
diff --git a/api/tests/favorites/test_serializers.py b/api/tests/favorites/test_serializers.py
new file mode 100644
index 000000000..16823caa3
--- /dev/null
+++ b/api/tests/favorites/test_serializers.py
@@ -0,0 +1,20 @@
+from funkwhale_api.federation import serializers as federation_serializers
+from funkwhale_api.favorites import serializers
+from funkwhale_api.music import serializers as music_serializers
+from funkwhale_api.users import serializers as users_serializers
+
+
+def test_track_favorite_serializer(factories, to_api_date):
+ favorite = factories["favorites.TrackFavorite"]()
+ actor = favorite.user.create_actor()
+
+ expected = {
+ "id": favorite.pk,
+ "creation_date": to_api_date(favorite.creation_date),
+ "track": music_serializers.TrackSerializer(favorite.track).data,
+ "actor": federation_serializers.APIActorSerializer(actor).data,
+ "user": users_serializers.UserBasicSerializer(favorite.user).data,
+ }
+ serializer = serializers.UserTrackFavoriteSerializer(favorite)
+
+ assert serializer.data == expected
diff --git a/api/tests/history/test_serializers.py b/api/tests/history/test_serializers.py
new file mode 100644
index 000000000..170b44d6b
--- /dev/null
+++ b/api/tests/history/test_serializers.py
@@ -0,0 +1,20 @@
+from funkwhale_api.federation import serializers as federation_serializers
+from funkwhale_api.history import serializers
+from funkwhale_api.music import serializers as music_serializers
+from funkwhale_api.users import serializers as users_serializers
+
+
+def test_listening_serializer(factories, to_api_date):
+ listening = factories["history.Listening"]()
+ actor = listening.user.create_actor()
+
+ expected = {
+ "id": listening.pk,
+ "creation_date": to_api_date(listening.creation_date),
+ "track": music_serializers.TrackSerializer(listening.track).data,
+ "actor": federation_serializers.APIActorSerializer(actor).data,
+ "user": users_serializers.UserBasicSerializer(listening.user).data,
+ }
+ serializer = serializers.ListeningSerializer(listening)
+
+ assert serializer.data == expected
diff --git a/api/tests/instance/test_nodeinfo.py b/api/tests/instance/test_nodeinfo.py
index 211dbaa54..cdb9ad93a 100644
--- a/api/tests/instance/test_nodeinfo.py
+++ b/api/tests/instance/test_nodeinfo.py
@@ -8,6 +8,12 @@ from funkwhale_api.music import utils as music_utils
def test_nodeinfo_dump(preferences, mocker):
preferences["instance__nodeinfo_stats_enabled"] = True
+ preferences["moderation__unauthenticated_report_types"] = [
+ "takedown_request",
+ "other",
+ "other_category_that_doesnt_exist",
+ ]
+
stats = {
"users": {"total": 1, "active_halfyear": 12, "active_month": 13},
"tracks": 2,
@@ -51,6 +57,29 @@ def test_nodeinfo_dump(preferences, mocker):
},
"supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS,
"allowList": {"enabled": False, "domains": None},
+ "reportTypes": [
+ {
+ "type": "takedown_request",
+ "label": "Takedown request",
+ "anonymous": True,
+ },
+ {
+ "type": "invalid_metadata",
+ "label": "Invalid metadata",
+ "anonymous": False,
+ },
+ {
+ "type": "illegal_content",
+ "label": "Illegal content",
+ "anonymous": False,
+ },
+ {
+ "type": "offensive_content",
+ "label": "Offensive content",
+ "anonymous": False,
+ },
+ {"type": "other", "label": "Other", "anonymous": True},
+ ],
},
}
assert nodeinfo.get() == expected
@@ -58,6 +87,10 @@ def test_nodeinfo_dump(preferences, mocker):
def test_nodeinfo_dump_stats_disabled(preferences, mocker):
preferences["instance__nodeinfo_stats_enabled"] = False
+ preferences["moderation__unauthenticated_report_types"] = [
+ "takedown_request",
+ "other",
+ ]
expected = {
"version": "2.0",
@@ -83,6 +116,29 @@ def test_nodeinfo_dump_stats_disabled(preferences, mocker):
},
"supportedUploadExtensions": music_utils.SUPPORTED_EXTENSIONS,
"allowList": {"enabled": False, "domains": None},
+ "reportTypes": [
+ {
+ "type": "takedown_request",
+ "label": "Takedown request",
+ "anonymous": True,
+ },
+ {
+ "type": "invalid_metadata",
+ "label": "Invalid metadata",
+ "anonymous": False,
+ },
+ {
+ "type": "illegal_content",
+ "label": "Illegal content",
+ "anonymous": False,
+ },
+ {
+ "type": "offensive_content",
+ "label": "Offensive content",
+ "anonymous": False,
+ },
+ {"type": "other", "label": "Other", "anonymous": True},
+ ],
},
}
assert nodeinfo.get() == expected
diff --git a/api/tests/playlists/test_serializers.py b/api/tests/playlists/test_serializers.py
index 250094729..f84df4bb2 100644
--- a/api/tests/playlists/test_serializers.py
+++ b/api/tests/playlists/test_serializers.py
@@ -1,4 +1,6 @@
+from funkwhale_api.federation import serializers as federation_serializers
from funkwhale_api.playlists import models, serializers
+from funkwhale_api.users import serializers as users_serializers
def test_cannot_max_500_tracks_per_playlist(factories, preferences):
@@ -124,3 +126,25 @@ def test_playlist_serializer_include_duration(factories, api_request):
serializer = serializers.PlaylistSerializer(qs.get())
assert serializer.data["duration"] == 45
+
+
+def test_playlist_serializer(factories, to_api_date):
+ playlist = factories["playlists.Playlist"]()
+ actor = playlist.user.create_actor()
+
+ expected = {
+ "id": playlist.pk,
+ "name": playlist.name,
+ "privacy_level": playlist.privacy_level,
+ "is_playable": None,
+ "creation_date": to_api_date(playlist.creation_date),
+ "modification_date": to_api_date(playlist.modification_date),
+ "actor": federation_serializers.APIActorSerializer(actor).data,
+ "user": users_serializers.UserBasicSerializer(playlist.user).data,
+ "duration": 0,
+ "tracks_count": 0,
+ "album_covers": [],
+ }
+ serializer = serializers.PlaylistSerializer(playlist)
+
+ assert serializer.data == expected
diff --git a/front/src/App.vue b/front/src/App.vue
index e401d475e..ad64cb0b5 100644
--- a/front/src/App.vue
+++ b/front/src/App.vue
@@ -21,6 +21,7 @@
>