diff --git a/api/funkwhale_api/common/models.py b/api/funkwhale_api/common/models.py
index efb2cf4fe..87f7dc8e3 100644
--- a/api/funkwhale_api/common/models.py
+++ b/api/funkwhale_api/common/models.py
@@ -4,6 +4,7 @@ from django.contrib.postgres.fields import JSONField
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.conf import settings
+from django.core.serializers.json import DjangoJSONEncoder
from django.db import models, transaction
from django.db.models import Lookup
from django.db.models.fields import Field
@@ -70,8 +71,8 @@ class Mutation(models.Model):
applied_date = models.DateTimeField(null=True, blank=True, db_index=True)
summary = models.TextField(max_length=2000, null=True, blank=True)
- payload = JSONField()
- previous_state = JSONField(null=True, default=None)
+ payload = JSONField(encoder=DjangoJSONEncoder)
+ previous_state = JSONField(null=True, default=None, encoder=DjangoJSONEncoder)
target_id = models.IntegerField(null=True)
target_content_type = models.ForeignKey(
diff --git a/api/funkwhale_api/federation/routes.py b/api/funkwhale_api/federation/routes.py
index 9f14fd110..bae2812ce 100644
--- a/api/funkwhale_api/federation/routes.py
+++ b/api/funkwhale_api/federation/routes.py
@@ -346,3 +346,37 @@ def outbox_update_track(context):
to=[activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}],
),
}
+
+
+@outbox.register({"type": "Update", "object.type": "Album"})
+def outbox_update_album(context):
+ album = context["album"]
+ serializer = serializers.ActivitySerializer(
+ {"type": "Update", "object": serializers.AlbumSerializer(album).data}
+ )
+
+ yield {
+ "type": "Update",
+ "actor": actors.get_service_actor(),
+ "payload": with_recipients(
+ serializer.data,
+ to=[activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}],
+ ),
+ }
+
+
+@outbox.register({"type": "Update", "object.type": "Artist"})
+def outbox_update_artist(context):
+ artist = context["artist"]
+ serializer = serializers.ActivitySerializer(
+ {"type": "Update", "object": serializers.ArtistSerializer(artist).data}
+ )
+
+ yield {
+ "type": "Update",
+ "actor": actors.get_service_actor(),
+ "payload": with_recipients(
+ serializer.data,
+ to=[activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}],
+ ),
+ }
diff --git a/api/funkwhale_api/music/mutations.py b/api/funkwhale_api/music/mutations.py
index fdbb7c11c..9fd91fb50 100644
--- a/api/funkwhale_api/music/mutations.py
+++ b/api/funkwhale_api/music/mutations.py
@@ -28,3 +28,35 @@ class TrackMutationSerializer(mutations.UpdateMutationSerializer):
routes.outbox.dispatch(
{"type": "Update", "object": {"type": "Track"}}, context={"track": obj}
)
+
+
+@mutations.registry.connect(
+ "update",
+ models.Artist,
+ perm_checkers={"suggest": can_suggest, "approve": can_approve},
+)
+class ArtistMutationSerializer(mutations.UpdateMutationSerializer):
+ class Meta:
+ model = models.Artist
+ fields = ["name"]
+
+ def post_apply(self, obj, validated_data):
+ routes.outbox.dispatch(
+ {"type": "Update", "object": {"type": "Artist"}}, context={"artist": obj}
+ )
+
+
+@mutations.registry.connect(
+ "update",
+ models.Album,
+ perm_checkers={"suggest": can_suggest, "approve": can_approve},
+)
+class AlbumMutationSerializer(mutations.UpdateMutationSerializer):
+ class Meta:
+ model = models.Album
+ fields = ["title", "release_date"]
+
+ def post_apply(self, obj, validated_data):
+ routes.outbox.dispatch(
+ {"type": "Update", "object": {"type": "Album"}}, context={"album": obj}
+ )
diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index eeaa80124..b6df22143 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -70,6 +70,8 @@ class ArtistViewSet(common_views.SkipFilterForGetObject, viewsets.ReadOnlyModelV
filterset_class = filters.ArtistFilter
ordering_fields = ("id", "name", "creation_date")
+ mutations = common_decorators.mutations_route(types=["update"])
+
def get_queryset(self):
queryset = super().get_queryset()
albums = models.Album.objects.with_tracks_count()
@@ -98,6 +100,8 @@ class AlbumViewSet(common_views.SkipFilterForGetObject, viewsets.ReadOnlyModelVi
ordering_fields = ("creation_date", "release_date", "title")
filterset_class = filters.AlbumFilter
+ mutations = common_decorators.mutations_route(types=["update"])
+
def get_queryset(self):
queryset = super().get_queryset()
tracks = (
diff --git a/api/tests/federation/test_routes.py b/api/tests/federation/test_routes.py
index 10b580829..5dfef61d3 100644
--- a/api/tests/federation/test_routes.py
+++ b/api/tests/federation/test_routes.py
@@ -448,6 +448,19 @@ def test_inbox_update_artist(factories, mocker):
update_library_entity.assert_called_once_with(obj, {"name": "New name"})
+def test_outbox_update_artist(factories):
+ artist = factories["music.Artist"]()
+ activity = list(routes.outbox_update_artist({"artist": artist}))[0]
+ expected = serializers.ActivitySerializer(
+ {"type": "Update", "object": serializers.ArtistSerializer(artist).data}
+ ).data
+
+ expected["to"] = [contexts.AS.Public, {"type": "instances_with_followers"}]
+
+ assert dict(activity["payload"]) == dict(expected)
+ assert activity["actor"] == actors.get_service_actor()
+
+
def test_inbox_update_album(factories, mocker):
update_library_entity = mocker.patch(
"funkwhale_api.music.tasks.update_library_entity"
@@ -466,6 +479,19 @@ def test_inbox_update_album(factories, mocker):
update_library_entity.assert_called_once_with(obj, {"title": "New title"})
+def test_outbox_update_album(factories):
+ album = factories["music.Album"]()
+ activity = list(routes.outbox_update_album({"album": album}))[0]
+ expected = serializers.ActivitySerializer(
+ {"type": "Update", "object": serializers.AlbumSerializer(album).data}
+ ).data
+
+ expected["to"] = [contexts.AS.Public, {"type": "instances_with_followers"}]
+
+ assert dict(activity["payload"]) == dict(expected)
+ assert activity["actor"] == actors.get_service_actor()
+
+
def test_inbox_update_track(factories, mocker):
update_library_entity = mocker.patch(
"funkwhale_api.music.tasks.update_library_entity"
diff --git a/api/tests/music/test_mutations.py b/api/tests/music/test_mutations.py
index a8a529798..be3fb0d76 100644
--- a/api/tests/music/test_mutations.py
+++ b/api/tests/music/test_mutations.py
@@ -1,6 +1,54 @@
+import datetime
+import pytest
+
from funkwhale_api.music import licenses
+@pytest.mark.parametrize(
+ "field, old_value, new_value, expected", [("name", "foo", "bar", "bar")]
+)
+def test_artist_mutation(field, old_value, new_value, expected, factories, now, mocker):
+ dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
+ artist = factories["music.Artist"](**{field: old_value})
+ mutation = factories["common.Mutation"](
+ type="update", target=artist, payload={field: new_value}
+ )
+ mutation.apply()
+ artist.refresh_from_db()
+
+ assert getattr(artist, field) == expected
+ dispatch.assert_called_once_with(
+ {"type": "Update", "object": {"type": "Artist"}}, context={"artist": artist}
+ )
+
+
+@pytest.mark.parametrize(
+ "field, old_value, new_value, expected",
+ [
+ ("title", "foo", "bar", "bar"),
+ (
+ "release_date",
+ datetime.date(2016, 1, 1),
+ "2018-02-01",
+ datetime.date(2018, 2, 1),
+ ),
+ ],
+)
+def test_album_mutation(field, old_value, new_value, expected, factories, now, mocker):
+ dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
+ album = factories["music.Album"](**{field: old_value})
+ mutation = factories["common.Mutation"](
+ type="update", target=album, payload={field: new_value}
+ )
+ mutation.apply()
+ album.refresh_from_db()
+
+ assert getattr(album, field) == expected
+ dispatch.assert_called_once_with(
+ {"type": "Update", "object": {"type": "Album"}}, context={"album": album}
+ )
+
+
def test_track_license_mutation(factories, now):
track = factories["music.Track"](license=None)
mutation = factories["common.Mutation"](
diff --git a/front/src/components/library/Album.vue b/front/src/components/library/AlbumBase.vue
similarity index 65%
rename from front/src/components/library/Album.vue
rename to front/src/components/library/AlbumBase.vue
index 1a5f6b50d..3ff07b10a 100644
--- a/front/src/components/library/Album.vue
+++ b/front/src/components/library/AlbumBase.vue
@@ -1,15 +1,15 @@
@@ -17,7 +17,7 @@
-
-
-
-
+
+
+
+
+
+
+