Commit 668615a8 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Merge branch '594-scope-api' into 'develop'

API support for restricting results by scope

See merge request funkwhale/funkwhale!921
parents c8218c80 6d84a814
......@@ -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})
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"
]
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"]
......@@ -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):
......
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):
......
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",
}
......@@ -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
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment