See #432: can now suggest tags on tracks/albums/artists (API)

This commit is contained in:
Eliot Berriot 2019-07-15 12:12:22 +02:00
parent 1b34ae2335
commit 2bbb2f3239
No known key found for this signature in database
GPG Key ID: DD6965E2476E5C27
4 changed files with 54 additions and 9 deletions

View File

@ -86,6 +86,7 @@ class MutationSerializer(serializers.Serializer):
class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer): class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer):
serialized_relations = {} serialized_relations = {}
previous_state_handlers = {}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# we force partial mode, because update mutations are partial # we force partial mode, because update mutations are partial
@ -139,16 +140,20 @@ class UpdateMutationSerializer(serializers.ModelSerializer, MutationSerializer):
return get_update_previous_state( return get_update_previous_state(
obj, obj,
*list(validated_data.keys()), *list(validated_data.keys()),
serialized_relations=self.serialized_relations serialized_relations=self.serialized_relations,
handlers=self.previous_state_handlers,
) )
def get_update_previous_state(obj, *fields, serialized_relations={}): def get_update_previous_state(obj, *fields, serialized_relations={}, handlers={}):
if not fields: if not fields:
raise ValueError("You need to provide at least one field") raise ValueError("You need to provide at least one field")
state = {} state = {}
for field in fields: for field in fields:
if field in handlers:
state[field] = handlers[field](obj)
continue
value = getattr(obj, field) value = getattr(obj, field)
if isinstance(value, models.Model): if isinstance(value, models.Model):
# we store the related object id and repr for better UX # we store the related object id and repr for better UX

View File

@ -1,5 +1,7 @@
from funkwhale_api.common import mutations from funkwhale_api.common import mutations
from funkwhale_api.federation import routes from funkwhale_api.federation import routes
from funkwhale_api.tags import models as tags_models
from funkwhale_api.tags import serializers as tags_serializers
from . import models from . import models
@ -12,17 +14,32 @@ def can_approve(obj, actor):
return obj.is_local and actor.user and actor.user.get_permissions()["library"] return obj.is_local and actor.user and actor.user.get_permissions()["library"]
class TagMutation(mutations.UpdateMutationSerializer):
tags = tags_serializers.TagsListField()
previous_state_handlers = {
"tags": lambda obj: list(
sorted(obj.tagged_items.values_list("tag__name", flat=True))
)
}
def update(self, instance, validated_data):
tags = validated_data.pop("tags", [])
r = super().update(instance, validated_data)
tags_models.set_tags(instance, *tags)
return r
@mutations.registry.connect( @mutations.registry.connect(
"update", "update",
models.Track, models.Track,
perm_checkers={"suggest": can_suggest, "approve": can_approve}, perm_checkers={"suggest": can_suggest, "approve": can_approve},
) )
class TrackMutationSerializer(mutations.UpdateMutationSerializer): class TrackMutationSerializer(TagMutation):
serialized_relations = {"license": "code"} serialized_relations = {"license": "code"}
class Meta: class Meta:
model = models.Track model = models.Track
fields = ["license", "title", "position", "copyright"] fields = ["license", "title", "position", "copyright", "tags"]
def post_apply(self, obj, validated_data): def post_apply(self, obj, validated_data):
routes.outbox.dispatch( routes.outbox.dispatch(
@ -35,10 +52,10 @@ class TrackMutationSerializer(mutations.UpdateMutationSerializer):
models.Artist, models.Artist,
perm_checkers={"suggest": can_suggest, "approve": can_approve}, perm_checkers={"suggest": can_suggest, "approve": can_approve},
) )
class ArtistMutationSerializer(mutations.UpdateMutationSerializer): class ArtistMutationSerializer(TagMutation):
class Meta: class Meta:
model = models.Artist model = models.Artist
fields = ["name"] fields = ["name", "tags"]
def post_apply(self, obj, validated_data): def post_apply(self, obj, validated_data):
routes.outbox.dispatch( routes.outbox.dispatch(
@ -51,10 +68,10 @@ class ArtistMutationSerializer(mutations.UpdateMutationSerializer):
models.Album, models.Album,
perm_checkers={"suggest": can_suggest, "approve": can_approve}, perm_checkers={"suggest": can_suggest, "approve": can_approve},
) )
class AlbumMutationSerializer(mutations.UpdateMutationSerializer): class AlbumMutationSerializer(TagMutation):
class Meta: class Meta:
model = models.Album model = models.Album
fields = ["title", "release_date"] fields = ["title", "release_date", "tags"]
def post_apply(self, obj, validated_data): def post_apply(self, obj, validated_data):
routes.outbox.dispatch( routes.outbox.dispatch(

View File

@ -51,7 +51,7 @@ def test_apply_update_mutation(factories, mutations_registry, mocker):
) )
assert previous_state == get_update_previous_state.return_value assert previous_state == get_update_previous_state.return_value
get_update_previous_state.assert_called_once_with( get_update_previous_state.assert_called_once_with(
user, "username", serialized_relations={} user, "username", serialized_relations={}, handlers={}
) )
user.refresh_from_db() user.refresh_from_db()

View File

@ -2,6 +2,7 @@ import datetime
import pytest import pytest
from funkwhale_api.music import licenses from funkwhale_api.music import licenses
from funkwhale_api.tags import models as tags_models
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -117,3 +118,25 @@ def test_track_mutation_apply_outbox(factories, mocker):
dispatch.assert_called_once_with( dispatch.assert_called_once_with(
{"type": "Update", "object": {"type": "Track"}}, context={"track": track} {"type": "Update", "object": {"type": "Track"}}, context={"track": track}
) )
@pytest.mark.parametrize("factory_name", ["music.Artist", "music.Album", "music.Track"])
def test_mutation_set_tags(factory_name, factories, now, mocker):
tags = ["tag1", "tag2"]
dispatch = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
set_tags = mocker.spy(tags_models, "set_tags")
obj = factories[factory_name]()
assert obj.tagged_items.all().count() == 0
mutation = factories["common.Mutation"](
type="update", target=obj, payload={"tags": tags}
)
mutation.apply()
obj.refresh_from_db()
assert sorted(obj.tagged_items.all().values_list("tag__name", flat=True)) == tags
set_tags.assert_called_once_with(obj, *tags)
obj_type = factory_name.lstrip("music.")
dispatch.assert_called_once_with(
{"type": "Update", "object": {"type": obj_type}},
context={obj_type.lower(): obj},
)