kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
Fix #1116: Can now filter subscribed content through API
rodzic
c32283659a
commit
ca056f717e
|
@ -7,6 +7,7 @@ from django_filters import rest_framework as filters
|
|||
from . import fields
|
||||
from . import models
|
||||
from . import search
|
||||
from . import utils
|
||||
|
||||
|
||||
class NoneObject(object):
|
||||
|
@ -170,13 +171,17 @@ class MutationFilter(filters.FilterSet):
|
|||
fields = ["is_approved", "is_applied", "type"]
|
||||
|
||||
|
||||
class EmptyQuerySet(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class ActorScopeFilter(filters.CharFilter):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.actor_field = kwargs.pop("actor_field")
|
||||
self.library_field = kwargs.pop("library_field", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def filter(self, queryset, value):
|
||||
from funkwhale_api.federation import models as federation_models
|
||||
|
||||
if not value:
|
||||
return queryset
|
||||
|
@ -186,35 +191,57 @@ class ActorScopeFilter(filters.CharFilter):
|
|||
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
|
||||
elif value.lower().startswith("actor:"):
|
||||
full_username = value.split("actor:", 1)[1]
|
||||
actor = getattr(user, "actor", None)
|
||||
scopes = [v.strip().lower() for v in value.split(",")]
|
||||
query = None
|
||||
for scope in scopes:
|
||||
try:
|
||||
right_query = self.get_query(scope, user, actor)
|
||||
except ValueError:
|
||||
return queryset.none()
|
||||
query = utils.join_queries_or(query, right_query)
|
||||
|
||||
return queryset.filter(query).distinct()
|
||||
|
||||
def get_query(self, scope, user, actor):
|
||||
from funkwhale_api.federation import models as federation_models
|
||||
|
||||
if scope == "me":
|
||||
return self.filter_me(actor)
|
||||
elif scope == "all":
|
||||
return Q(pk__gte=0)
|
||||
|
||||
elif scope == "subscribed":
|
||||
if not actor or self.library_field is None:
|
||||
raise EmptyQuerySet()
|
||||
followed_libraries = federation_models.LibraryFollow.objects.filter(
|
||||
approved=True, actor=user.actor
|
||||
).values_list("target_id", flat=True)
|
||||
if not self.library_field:
|
||||
predicate = "pk__in"
|
||||
else:
|
||||
predicate = "{}__in".format(self.library_field)
|
||||
return Q(**{predicate: followed_libraries})
|
||||
|
||||
elif scope.startswith("actor:"):
|
||||
full_username = scope.split("actor:", 1)[1]
|
||||
username, domain = full_username.split("@")
|
||||
try:
|
||||
actor = federation_models.Actor.objects.get(
|
||||
preferred_username=username, domain_id=domain,
|
||||
)
|
||||
except federation_models.Actor.DoesNotExist:
|
||||
return queryset.none()
|
||||
raise EmptyQuerySet()
|
||||
|
||||
return queryset.filter(**{self.actor_field: actor})
|
||||
elif value.lower().startswith("domain:"):
|
||||
domain = value.split("domain:", 1)[1]
|
||||
return queryset.filter(**{"{}__domain_id".format(self.actor_field): domain})
|
||||
return Q(**{self.actor_field: actor})
|
||||
elif scope.startswith("domain:"):
|
||||
domain = scope.split("domain:", 1)[1]
|
||||
return Q(**{"{}__domain_id".format(self.actor_field): domain})
|
||||
else:
|
||||
return queryset.none()
|
||||
raise EmptyQuerySet()
|
||||
|
||||
if self.distinct:
|
||||
qs = qs.distinct()
|
||||
return qs
|
||||
|
||||
def filter_me(self, user, queryset):
|
||||
actor = getattr(user, "actor", None)
|
||||
def filter_me(self, actor):
|
||||
if not actor:
|
||||
return queryset.none()
|
||||
raise EmptyQuerySet()
|
||||
|
||||
return queryset.filter(**{self.actor_field: actor})
|
||||
return Q(**{self.actor_field: actor})
|
||||
|
|
|
@ -148,7 +148,9 @@ class TrackFilter(
|
|||
tag = TAG_FILTER
|
||||
id = common_filters.MultipleQueryFilter(coerce=int)
|
||||
scope = common_filters.ActorScopeFilter(
|
||||
actor_field="uploads__library__actor", distinct=True
|
||||
actor_field="uploads__library__actor",
|
||||
library_field="uploads__library",
|
||||
distinct=True,
|
||||
)
|
||||
artist = filters.ModelChoiceFilter(
|
||||
field_name="_", method="filter_artist", queryset=models.Artist.objects.all()
|
||||
|
|
|
@ -44,15 +44,20 @@ def test_mutation_filter_is_approved(value, expected, factories):
|
|||
("me", 0, [0]),
|
||||
("me", 1, [1]),
|
||||
("me", 2, []),
|
||||
("all", 0, [0, 1, 2]),
|
||||
("all", 1, [0, 1, 2]),
|
||||
("all", 2, [0, 1, 2]),
|
||||
("all", 0, [0, 1, 2, 3]),
|
||||
("all", 1, [0, 1, 2, 3]),
|
||||
("all", 2, [0, 1, 2, 3]),
|
||||
("noop", 0, []),
|
||||
("noop", 1, []),
|
||||
("noop", 2, []),
|
||||
("actor:actor1@domain.test", 0, [0]),
|
||||
("actor:actor2@domain.test", 0, [1]),
|
||||
("domain:domain.test", 0, [0, 1]),
|
||||
("subscribed", 0, [3]),
|
||||
("subscribed", 1, []),
|
||||
("subscribed", 2, []),
|
||||
("me,subscribed", 0, [0, 3]),
|
||||
("me,subscribed", 1, [1]),
|
||||
],
|
||||
)
|
||||
def test_actor_scope_filter(
|
||||
|
@ -72,15 +77,23 @@ def test_actor_scope_filter(
|
|||
preferred_username="actor2", domain=domain
|
||||
)
|
||||
users = [actor1.user, actor2.user, anonymous_user]
|
||||
followed_library = factories["music.Library"]()
|
||||
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,
|
||||
factories["music.Upload"](playable=True, library=followed_library).track,
|
||||
]
|
||||
|
||||
factories["federation.LibraryFollow"](
|
||||
actor=actor1, target=followed_library, approved=True
|
||||
)
|
||||
|
||||
class FS(filters.filters.FilterSet):
|
||||
scope = filters.ActorScopeFilter(
|
||||
actor_field="uploads__library__actor", distinct=True
|
||||
actor_field="uploads__library__actor",
|
||||
library_field="uploads__library",
|
||||
distinct=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Can now filter subscribed content through API (#1116)
|
|
@ -76,14 +76,19 @@ Scope:
|
|||
Limit the results to a given user or pod:
|
||||
- Use `all` (or do not specify the property to disable scope filtering)
|
||||
- Use `me` to retrieve content relative to the current user
|
||||
- Use `subscribed` to retrieve content in libraries you follow
|
||||
- Use `actor:alice@example.com` to retrieve content relative to the account `alice@example.com
|
||||
- Use `domain:example.com` to retrieve content relative to the domain `example.com
|
||||
|
||||
You can specify multiple coma separated scopes, e.g `scope=me,subscribed` to retrieve content matching either scopes.
|
||||
|
||||
schema:
|
||||
required: false
|
||||
type: "string"
|
||||
enum:
|
||||
- "me"
|
||||
- "all"
|
||||
- "subscribed"
|
||||
- "actor:alice@example.com"
|
||||
- "domain:example.com"
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue