diff --git a/api/config/api_urls.py b/api/config/api_urls.py index 2631309eb..da1981585 100644 --- a/api/config/api_urls.py +++ b/api/config/api_urls.py @@ -10,11 +10,12 @@ from funkwhale_api.common import routers as common_routers from funkwhale_api.music import views from funkwhale_api.playlists import views as playlists_views from funkwhale_api.subsonic.views import SubsonicViewSet +from funkwhale_api.tags import views as tags_views router = common_routers.OptionalSlashRouter() router.register(r"settings", GlobalPreferencesViewSet, basename="settings") router.register(r"activity", activity_views.ActivityViewSet, "activity") -router.register(r"tags", views.TagViewSet, "tags") +router.register(r"tags", tags_views.TagViewSet, "tags") router.register(r"tracks", views.TrackViewSet, "tracks") router.register(r"uploads", views.UploadViewSet, "uploads") router.register(r"libraries", views.LibraryViewSet, "libraries") diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index 9aa792480..5b34c3ce2 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -447,14 +447,6 @@ class UploadViewSet( instance.delete() -class TagViewSet(viewsets.ReadOnlyModelViewSet): - queryset = Tag.objects.all().order_by("name") - serializer_class = serializers.TagSerializer - permission_classes = [oauth_permissions.ScopePermission] - required_scope = "libraries" - anonymous_policy = "setting" - - class Search(views.APIView): max_results = 3 permission_classes = [oauth_permissions.ScopePermission] diff --git a/api/funkwhale_api/tags/filters.py b/api/funkwhale_api/tags/filters.py new file mode 100644 index 000000000..4be4afeef --- /dev/null +++ b/api/funkwhale_api/tags/filters.py @@ -0,0 +1,21 @@ +import django_filters +from django_filters import rest_framework as filters + +from funkwhale_api.common import fields + +from . import models + + +class TagFilter(filters.FilterSet): + q = fields.SearchFilter(search_fields=["name"]) + ordering = django_filters.OrderingFilter( + fields=( + ("name", "name"), + ("creation_date", "creation_date"), + ("__size", "length"), + ) + ) + + class Meta: + model = models.Tag + fields = ["q"] diff --git a/api/funkwhale_api/tags/serializers.py b/api/funkwhale_api/tags/serializers.py new file mode 100644 index 000000000..f656f856c --- /dev/null +++ b/api/funkwhale_api/tags/serializers.py @@ -0,0 +1,9 @@ +from rest_framework import serializers + +from . import models + + +class TagSerializer(serializers.ModelSerializer): + class Meta: + model = models.Tag + fields = ["name", "creation_date"] diff --git a/api/funkwhale_api/tags/views.py b/api/funkwhale_api/tags/views.py new file mode 100644 index 000000000..d7b1d8aa5 --- /dev/null +++ b/api/funkwhale_api/tags/views.py @@ -0,0 +1,22 @@ +from django.db.models import functions +from rest_framework import viewsets + +from funkwhale_api.users.oauth import permissions as oauth_permissions + +from . import filters +from . import models +from . import serializers + + +class TagViewSet(viewsets.ReadOnlyModelViewSet): + lookup_field = "name" + queryset = ( + models.Tag.objects.all() + .annotate(__size=functions.Length("name")) + .order_by("name") + ) + serializer_class = serializers.TagSerializer + permission_classes = [oauth_permissions.ScopePermission] + required_scope = "libraries" + anonymous_policy = "setting" + filterset_class = filters.TagFilter diff --git a/api/tests/tags/test_filters.py b/api/tests/tags/test_filters.py new file mode 100644 index 000000000..3b812f0fe --- /dev/null +++ b/api/tests/tags/test_filters.py @@ -0,0 +1,16 @@ +from funkwhale_api.tags import filters +from funkwhale_api.tags import models + + +def test_filter_search_tag(factories, queryset_equal_list): + matches = [ + factories["tags.Tag"](name="Tag1"), + factories["tags.Tag"](name="TestTag1"), + factories["tags.Tag"](name="TestTag12"), + ] + factories["tags.Tag"](name="TestTag") + factories["tags.Tag"](name="TestTag2") + qs = models.Tag.objects.all().order_by("name") + filterset = filters.TagFilter({"q": "tag1"}, queryset=qs) + + assert filterset.qs == matches diff --git a/api/tests/tags/test_serializers.py b/api/tests/tags/test_serializers.py new file mode 100644 index 000000000..8909a9edc --- /dev/null +++ b/api/tests/tags/test_serializers.py @@ -0,0 +1,14 @@ +from funkwhale_api.tags import serializers + + +def test_tag_serializer(factories): + tag = factories["tags.Tag"]() + + serializer = serializers.TagSerializer(tag) + + expected = { + "name": tag.name, + "creation_date": tag.creation_date.isoformat().split("+")[0] + "Z", + } + + assert serializer.data == expected diff --git a/api/tests/tags/test_views.py b/api/tests/tags/test_views.py new file mode 100644 index 000000000..fd3246adb --- /dev/null +++ b/api/tests/tags/test_views.py @@ -0,0 +1,50 @@ +from django.urls import reverse + +from funkwhale_api.tags import serializers + + +def test_tags_list(factories, logged_in_api_client): + url = reverse("api:v1:tags-list") + tag = factories["tags.Tag"]() + + expected = { + "count": 1, + "next": None, + "previous": None, + "results": [serializers.TagSerializer(tag).data], + } + + response = logged_in_api_client.get(url) + + assert response.data == expected + + +def test_tags_list_ordering_length(factories, logged_in_api_client): + url = reverse("api:v1:tags-list") + tags = [ + factories["tags.Tag"](name="iamareallylongtag"), + factories["tags.Tag"](name="reallylongtag"), + factories["tags.Tag"](name="short"), + factories["tags.Tag"](name="bar"), + ] + expected = { + "count": 4, + "next": None, + "previous": None, + "results": [serializers.TagSerializer(tag).data for tag in tags], + } + + response = logged_in_api_client.get(url, {"ordering": "-length"}) + + assert response.data == expected + + +def test_tags_detail(factories, logged_in_api_client): + tag = factories["tags.Tag"]() + url = reverse("api:v1:tags-detail", kwargs={"name": tag.name}) + + expected = serializers.TagSerializer(tag).data + + response = logged_in_api_client.get(url) + + assert response.data == expected diff --git a/api/tests/users/oauth/test_api_permissions.py b/api/tests/users/oauth/test_api_permissions.py index e73d3a3f9..b030803ee 100644 --- a/api/tests/users/oauth/test_api_permissions.py +++ b/api/tests/users/oauth/test_api_permissions.py @@ -35,6 +35,7 @@ from funkwhale_api.users.oauth import scopes "get", ), ("api:v1:federation:library-follows-list", {}, "read:follows", "get"), + ("api:v1:tags-list", {}, "read:libraries", "get"), # admin / privileged stuff ("api:v1:instance:admin-settings-list", {}, "read:instance:settings", "get"), (