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