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/common/management/commands/load_test_data.py b/api/funkwhale_api/common/management/commands/load_test_data.py index 2c7baeb7d..26f787f48 100644 --- a/api/funkwhale_api/common/management/commands/load_test_data.py +++ b/api/funkwhale_api/common/management/commands/load_test_data.py @@ -51,7 +51,7 @@ def create_tagged_tracks(factories, count, dependencies): objs = [] for track in dependencies["tracks"]: tag = random.choice(dependencies["tags"]) - objs.append(factories["tags.TaggedItem"](content_object=track, tag=tag)) + objs.append(factories["tags.TaggedItem"].build(content_object=track, tag=tag)) return tags_models.TaggedItem.objects.bulk_create( objs, batch_size=BATCH_SIZE, ignore_conflicts=True @@ -217,27 +217,24 @@ class Command(BaseCommand): if not count: return [] dependencies = row.get("depends_on", []) - dependencies_results = {} create_dependencies = options.get("create_dependencies") for dependency in dependencies: - if not create_dependencies: - continue dep_count = options.get(dependency["id"]) + if not create_dependencies and dep_count is None: + continue if dep_count is None: factor = options[ "{}_{}_factor".format(row["id"], dependency["field"]) ] or dependency.get("default_factor") dep_count = math.ceil(factor * count) - dependencies_results[dependency["id"]] = self.create_batch( + results[dependency["id"]] = self.create_batch( CONFIG_BY_ID[dependency["id"]], results, options, count=dep_count ) self.stdout.write("Creating {} {}…".format(count, row["id"])) handler = row.get("handler") if handler: - objects = handler( - factories.registry, count, dependencies=dependencies_results - ) + objects = handler(factories.registry, count, dependencies=results) else: objects = create_objects( row, factories.registry, count, **row.get("factory_kwargs", {}) @@ -246,7 +243,7 @@ class Command(BaseCommand): if not dependency.get("set", True): continue if create_dependencies: - candidates = dependencies_results[dependency["id"]] + candidates = results[dependency["id"]] else: # we use existing objects in the database queryset = dependency.get( 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"), (