See #432: can now suggest tags on tracks/albums/artists (API)
This commit is contained in:
parent
1b34ae2335
commit
2bbb2f3239
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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},
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue