diff --git a/api/funkwhale_api/common/filters.py b/api/funkwhale_api/common/filters.py index feca948bb6b71c6f388522ea5a1ca234e3b6b81d..953904bfa24480e0368def211ac4acbebea6e23b 100644 --- a/api/funkwhale_api/common/filters.py +++ b/api/funkwhale_api/common/filters.py @@ -168,3 +168,37 @@ class MutationFilter(filters.FilterSet): class Meta: model = models.Mutation fields = ["is_approved", "is_applied", "type"] + + +class ActorScopeFilter(filters.CharFilter): + def __init__(self, *args, **kwargs): + self.actor_field = kwargs.pop("actor_field") + super().__init__(*args, **kwargs) + + def filter(self, queryset, value): + if not value: + return queryset + + request = getattr(self.parent, "request", None) + if not request: + return queryset.none() + + user = getattr(request, "user", None) + qs = queryset + if value.lower() == "me": + qs = self.filter_me(user=user, queryset=queryset) + elif value.lower() == "all": + return queryset + else: + return queryset.none() + + if self.distinct: + qs = qs.distinct() + return qs + + def filter_me(self, user, queryset): + actor = getattr(user, "actor", None) + if not actor: + return queryset.none() + + return queryset.filter(**{self.actor_field: actor}) diff --git a/api/funkwhale_api/favorites/filters.py b/api/funkwhale_api/favorites/filters.py index cf8048b8d712d5e8da5dbab80df2093b29fbf794..8a4b91bb2ab717724a2094d5cc882849ed7f5042 100644 --- a/api/funkwhale_api/favorites/filters.py +++ b/api/funkwhale_api/favorites/filters.py @@ -1,4 +1,5 @@ from funkwhale_api.common import fields +from funkwhale_api.common import filters as common_filters from funkwhale_api.moderation import filters as moderation_filters from . import models @@ -8,10 +9,11 @@ class TrackFavoriteFilter(moderation_filters.HiddenContentFilterSet): q = fields.SearchFilter( search_fields=["track__title", "track__artist__name", "track__album__title"] ) + scope = common_filters.ActorScopeFilter(actor_field="user__actor", distinct=True) class Meta: model = models.TrackFavorite - fields = ["user", "q"] + fields = ["user", "q", "scope"] hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG[ "TRACK_FAVORITE" ] diff --git a/api/funkwhale_api/history/filters.py b/api/funkwhale_api/history/filters.py index 02549b3b10ea1c2e5c671ecb0652024dbf93989a..16a03204fbad744ca1c50cea55d2aed92e427fb7 100644 --- a/api/funkwhale_api/history/filters.py +++ b/api/funkwhale_api/history/filters.py @@ -1,5 +1,6 @@ import django_filters +from funkwhale_api.common import filters as common_filters from funkwhale_api.moderation import filters as moderation_filters from . import models @@ -8,10 +9,11 @@ from . import models class ListeningFilter(moderation_filters.HiddenContentFilterSet): username = django_filters.CharFilter("user__username") domain = django_filters.CharFilter("user__actor__domain_id") + scope = common_filters.ActorScopeFilter(actor_field="user__actor", distinct=True) class Meta: model = models.Listening hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG[ "LISTENING" ] - fields = ["hidden"] + fields = ["hidden", "scope"] diff --git a/api/funkwhale_api/music/filters.py b/api/funkwhale_api/music/filters.py index 44763b966ebe7a0eb02a28988b2213e725e1ffdb..f5bd17e67851827dd3cf9489faf73988908d4ec1 100644 --- a/api/funkwhale_api/music/filters.py +++ b/api/funkwhale_api/music/filters.py @@ -23,12 +23,17 @@ class ArtistFilter(moderation_filters.HiddenContentFilterSet): q = fields.SearchFilter(search_fields=["name"]) playable = filters.BooleanFilter(field_name="_", method="filter_playable") tag = TAG_FILTER + scope = common_filters.ActorScopeFilter( + actor_field="tracks__uploads__library__actor", distinct=True + ) class Meta: model = models.Artist fields = { "name": ["exact", "iexact", "startswith", "icontains"], - "playable": "exact", + "playable": ["exact"], + "scope": ["exact"], + "mbid": ["exact"], } hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["ARTIST"] @@ -42,6 +47,9 @@ class TrackFilter(moderation_filters.HiddenContentFilterSet): playable = filters.BooleanFilter(field_name="_", method="filter_playable") tag = TAG_FILTER id = common_filters.MultipleQueryFilter(coerce=int) + scope = common_filters.ActorScopeFilter( + actor_field="uploads__library__actor", distinct=True + ) class Meta: model = models.Track @@ -52,6 +60,8 @@ class TrackFilter(moderation_filters.HiddenContentFilterSet): "artist": ["exact"], "album": ["exact"], "license": ["exact"], + "scope": ["exact"], + "mbid": ["exact"], } hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["TRACK"] @@ -67,6 +77,7 @@ class UploadFilter(filters.FilterSet): album_artist = filters.UUIDFilter("track__album__artist__uuid") library = filters.UUIDFilter("library__uuid") playable = filters.BooleanFilter(field_name="_", method="filter_playable") + scope = common_filters.ActorScopeFilter(actor_field="library__actor", distinct=True) q = fields.SmartSearchFilter( config=search.SearchConfig( search_fields={ @@ -96,6 +107,7 @@ class UploadFilter(filters.FilterSet): "album_artist", "library", "import_reference", + "scope", ] def filter_playable(self, queryset, name, value): @@ -107,10 +119,13 @@ class AlbumFilter(moderation_filters.HiddenContentFilterSet): playable = filters.BooleanFilter(field_name="_", method="filter_playable") q = fields.SearchFilter(search_fields=["title", "artist__name"]) tag = TAG_FILTER + scope = common_filters.ActorScopeFilter( + actor_field="tracks__uploads__library__actor", distinct=True + ) class Meta: model = models.Album - fields = ["playable", "q", "artist"] + fields = ["playable", "q", "artist", "scope", "mbid"] hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["ALBUM"] def filter_playable(self, queryset, name, value): diff --git a/api/funkwhale_api/playlists/filters.py b/api/funkwhale_api/playlists/filters.py index b204df4b0a5ad7fe8cd5936e73bba10e2b928b54..43029a360ab9b78436bb85dfca9dc0ebb7ef0ceb 100644 --- a/api/funkwhale_api/playlists/filters.py +++ b/api/funkwhale_api/playlists/filters.py @@ -1,6 +1,7 @@ from django.db.models import Count from django_filters import rest_framework as filters +from funkwhale_api.common import filters as common_filters from funkwhale_api.music import utils from . import models @@ -9,6 +10,7 @@ from . import models class PlaylistFilter(filters.FilterSet): q = filters.CharFilter(field_name="_", method="filter_q") playable = filters.BooleanFilter(field_name="_", method="filter_playable") + scope = common_filters.ActorScopeFilter(actor_field="user__actor", distinct=True) class Meta: model = models.Playlist @@ -17,6 +19,7 @@ class PlaylistFilter(filters.FilterSet): "name": ["exact", "icontains"], "q": "exact", "playable": "exact", + "scope": "exact", } def filter_playable(self, queryset, name, value): diff --git a/api/funkwhale_api/radios/filtersets.py b/api/funkwhale_api/radios/filtersets.py index d8d7c9ed097f6c55fa67204e979fe7a3f15da8cf..6f548dbeaff579f6080ed2953788218fab705889 100644 --- a/api/funkwhale_api/radios/filtersets.py +++ b/api/funkwhale_api/radios/filtersets.py @@ -1,9 +1,15 @@ import django_filters +from funkwhale_api.common import filters as common_filters from . import models class RadioFilter(django_filters.FilterSet): + scope = common_filters.ActorScopeFilter(actor_field="user__actor", distinct=True) + class Meta: model = models.Radio - fields = {"name": ["exact", "iexact", "startswith", "icontains"]} + fields = { + "name": ["exact", "iexact", "startswith", "icontains"], + "scope": "exact", + } diff --git a/api/tests/common/test_filters.py b/api/tests/common/test_filters.py index 2e89dfa37c1dc2800569aca928b9452e0b2d29fe..138f6ca5d6d51b284658be015440d1bdbb9c2388 100644 --- a/api/tests/common/test_filters.py +++ b/api/tests/common/test_filters.py @@ -36,3 +36,53 @@ def test_mutation_filter_is_approved(value, expected, factories): ) assert list(filterset.qs) == [mutations[expected]] + + +@pytest.mark.parametrize( + "scope, user_index, expected_tracks", + [ + ("me", 0, [0]), + ("me", 1, [1]), + ("me", 2, []), + ("all", 0, [0, 1, 2]), + ("all", 1, [0, 1, 2]), + ("all", 2, [0, 1, 2]), + ("noop", 0, []), + ("noop", 1, []), + ("noop", 2, []), + ], +) +def test_actor_scope_filter( + scope, + user_index, + expected_tracks, + queryset_equal_list, + factories, + mocker, + anonymous_user, +): + actor1 = factories["users.User"]().create_actor() + actor2 = factories["users.User"]().create_actor() + users = [actor1.user, actor2.user, anonymous_user] + tracks = [ + factories["music.Upload"](library__actor=actor1, playable=True).track, + factories["music.Upload"](library__actor=actor2, playable=True).track, + factories["music.Upload"](playable=True).track, + ] + + class FS(filters.filters.FilterSet): + scope = filters.ActorScopeFilter( + actor_field="uploads__library__actor", distinct=True + ) + + class Meta: + model = tracks[0].__class__ + fields = ["scope"] + + queryset = tracks[0].__class__.objects.all() + request = mocker.Mock(user=users[user_index]) + filterset = FS({"scope": scope}, queryset=queryset.order_by("id"), request=request) + + expected = [tracks[i] for i in expected_tracks] + + assert filterset.qs == expected