funkwhale/api/funkwhale_api/music/filters.py

372 wiersze
12 KiB
Python

import django_filters
from django.db.models import Q
from django_filters import rest_framework as filters
from django_filters import widgets
from funkwhale_api.audio import filters as audio_filters
from funkwhale_api.audio import models as audio_models
from funkwhale_api.common import fields
from funkwhale_api.common import filters as common_filters
from funkwhale_api.common import search
from funkwhale_api.moderation import filters as moderation_filters
from funkwhale_api.tags import filters as tags_filters
from . import models, utils
def filter_tags(queryset, name, value):
non_empty_tags = [v.lower() for v in value if v]
for tag in non_empty_tags:
queryset = queryset.filter(tagged_items__tag__name=tag).distinct()
return queryset
TAG_FILTER = common_filters.MultipleQueryFilter(method=filter_tags)
class RelatedFilterSet(filters.FilterSet):
related_type = int
related_field = "pk"
related = filters.CharFilter(field_name="_", method="filter_related")
def filter_related(self, queryset, name, value):
if not value:
return queryset.none()
try:
pk = self.related_type(value)
except (TypeError, ValueError):
return queryset.none()
try:
obj = queryset.model.objects.get(**{self.related_field: pk})
except queryset.model.DoesNotExist:
return queryset.none()
queryset = queryset.exclude(pk=obj.pk)
return tags_filters.get_by_similar_tags(queryset, obj.get_tags())
class ChannelFilterSet(filters.FilterSet):
channel = filters.CharFilter(field_name="_", method="filter_channel")
def filter_channel(self, queryset, name, value):
if not value:
return queryset
channel = (
audio_models.Channel.objects.filter(uuid=value)
.select_related("library")
.first()
)
if not channel:
return queryset.none()
uploads = models.Upload.objects.filter(library=channel.library)
actor = utils.get_actor_from_request(self.request)
uploads = uploads.playable_by(actor)
ids = uploads.values_list(self.Meta.channel_filter_field, flat=True)
return queryset.filter(pk__in=ids).distinct()
class LibraryFilterSet(filters.FilterSet):
library = filters.CharFilter(field_name="_", method="filter_library")
def filter_library(self, queryset, name, value):
if not value:
return queryset
actor = utils.get_actor_from_request(self.request)
library = models.Library.objects.filter(uuid=value).viewable_by(actor).first()
if not library:
return queryset.none()
uploads = models.Upload.objects.filter(library=library)
uploads = uploads.playable_by(actor)
ids = uploads.values_list(self.Meta.library_filter_field, flat=True)
qs = queryset.filter(pk__in=ids).distinct()
return qs
class ArtistFilter(
RelatedFilterSet,
LibraryFilterSet,
audio_filters.IncludeChannelsFilterSet,
moderation_filters.HiddenContentFilterSet,
):
q = fields.SearchFilter(search_fields=["name"], fts_search_fields=["body_text"])
playable = filters.BooleanFilter(field_name="_", method="filter_playable")
has_albums = filters.BooleanFilter(field_name="_", method="filter_has_albums")
tag = TAG_FILTER
content_category = filters.CharFilter("content_category")
scope = common_filters.ActorScopeFilter(
actor_field="tracks__uploads__library__actor",
distinct=True,
library_field="tracks__uploads__library",
)
ordering = common_filters.CaseInsensitiveNameOrderingFilter(
fields=(
("id", "id"),
("name", "name"),
("creation_date", "creation_date"),
("modification_date", "modification_date"),
("?", "random"),
("tag_matches", "related"),
)
)
has_mbid = filters.BooleanFilter(
field_name="_",
method="filter_has_mbid",
)
class Meta:
model = models.Artist
fields = {
"name": ["exact", "iexact", "startswith", "icontains"],
"mbid": ["exact"],
}
hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["ARTIST"]
include_channels_field = "channel"
library_filter_field = "track__artist"
def filter_playable(self, queryset, name, value):
actor = utils.get_actor_from_request(self.request)
return queryset.playable_by(actor, value).distinct()
def filter_has_albums(self, queryset, name, value):
return queryset.filter(albums__isnull=not value)
def filter_has_mbid(self, queryset, name, value):
return queryset.filter(mbid__isnull=(not value))
class TrackFilter(
RelatedFilterSet,
ChannelFilterSet,
LibraryFilterSet,
audio_filters.IncludeChannelsFilterSet,
moderation_filters.HiddenContentFilterSet,
):
q = fields.SearchFilter(
search_fields=["title", "album__title", "artist__name"],
fts_search_fields=["body_text", "artist__body_text", "album__body_text"],
)
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",
library_field="uploads__library",
distinct=True,
)
artist = filters.ModelChoiceFilter(
field_name="_", method="filter_artist", queryset=models.Artist.objects.all()
)
ordering = django_filters.OrderingFilter(
fields=(
("creation_date", "creation_date"),
("title", "title"),
("album__title", "album__title"),
("album__release_date", "album__release_date"),
("size", "size"),
("position", "position"),
("disc_number", "disc_number"),
("artist__name", "artist__name"),
("artist__modification_date", "artist__modification_date"),
("?", "random"),
("tag_matches", "related"),
)
)
format = filters.CharFilter(
field_name="_",
method="filter_format",
)
has_mbid = filters.BooleanFilter(
field_name="_",
method="filter_has_mbid",
)
quality_choices = [(0, "low"), (1, "medium"), (2, "high"), (3, "very_high")]
quality = filters.ChoiceFilter(
choices=quality_choices,
method="filter_quality",
)
class Meta:
model = models.Track
fields = {
"title": ["exact", "iexact", "startswith", "icontains"],
"id": ["exact"],
"album": ["exact"],
"license": ["exact"],
"mbid": ["exact"],
}
hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["TRACK"]
include_channels_field = "artist__channel"
channel_filter_field = "track"
library_filter_field = "track"
def filter_playable(self, queryset, name, value):
actor = utils.get_actor_from_request(self.request)
return queryset.playable_by(actor, value).distinct()
def filter_artist(self, queryset, name, value):
return queryset.filter(Q(artist=value) | Q(album__artist=value))
def filter_format(self, queryset, name, value):
mimetypes = [utils.get_type_from_ext(e) for e in value.split(",")]
return queryset.filter(uploads__mimetype__in=mimetypes)
def filter_has_mbid(self, queryset, name, value):
return queryset.filter(mbid__isnull=(not value))
def filter_quality(self, queryset, name, value):
if value == "low":
return queryset.filter(upload__quality__gte=0)
if value == "medium":
return queryset.filter(upload__quality__gte=1)
if value == "high":
return queryset.filter(upload__quality__gte=2)
if value == "very-high":
return queryset.filter(upload__quality=3)
class UploadFilter(audio_filters.IncludeChannelsFilterSet):
library = filters.CharFilter("library__uuid")
channel = filters.CharFilter("library__channel__uuid")
track = filters.UUIDFilter("track__uuid")
track_artist = filters.UUIDFilter("track__artist__uuid")
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,
library_field="library",
)
import_status = common_filters.MultipleQueryFilter(coerce=str, distinct=False)
q = fields.SmartSearchFilter(
config=search.SearchConfig(
search_fields={
"track_artist": {"to": "track__artist__name"},
"album_artist": {"to": "track__album__artist__name"},
"album": {"to": "track__album__title"},
"title": {"to": "track__title"},
},
filter_fields={
"artist": {"to": "track__artist__name__iexact"},
"mimetype": {"to": "mimetype"},
"album": {"to": "track__album__title__iexact"},
"title": {"to": "track__title__iexact"},
"status": {"to": "import_status"},
},
)
)
class Meta:
model = models.Upload
fields = [
"import_status",
"mimetype",
"import_reference",
]
include_channels_field = "track__artist__channel"
def filter_playable(self, queryset, name, value):
actor = utils.get_actor_from_request(self.request)
return queryset.playable_by(actor, value)
class AlbumFilter(
RelatedFilterSet,
ChannelFilterSet,
LibraryFilterSet,
audio_filters.IncludeChannelsFilterSet,
moderation_filters.HiddenContentFilterSet,
):
playable = filters.BooleanFilter(field_name="_", method="filter_playable")
q = fields.SearchFilter(
search_fields=["title", "artist__name"],
fts_search_fields=["body_text", "artist__body_text"],
)
content_category = filters.CharFilter("artist__content_category")
tag = TAG_FILTER
scope = common_filters.ActorScopeFilter(
actor_field="tracks__uploads__library__actor",
distinct=True,
library_field="tracks__uploads__library",
)
ordering = django_filters.OrderingFilter(
fields=(
("creation_date", "creation_date"),
("release_date", "release_date"),
("title", "title"),
("artist__modification_date", "artist__modification_date"),
("?", "random"),
("tag_matches", "related"),
)
)
has_tags = filters.BooleanFilter(
field_name="_",
method="filter_has_tags",
)
has_mbid = filters.BooleanFilter(
field_name="_",
method="filter_has_mbid",
)
has_cover = filters.BooleanFilter(
field_name="_",
method="filter_has_cover",
)
has_release_date = filters.BooleanFilter(
field_name="_",
method="filter_has_release_date",
)
class Meta:
model = models.Album
fields = ["artist", "mbid"]
hidden_content_fields_mapping = moderation_filters.USER_FILTER_CONFIG["ALBUM"]
include_channels_field = "artist__channel"
channel_filter_field = "track__album"
library_filter_field = "track__album"
def filter_playable(self, queryset, name, value):
actor = utils.get_actor_from_request(self.request)
return queryset.playable_by(actor, value)
def filter_has_tags(self, queryset, name, value):
return queryset.filter(tagged_items__isnull=(not value))
def filter_has_mbid(self, queryset, name, value):
return queryset.filter(mbid__isnull=(not value))
def filter_has_cover(self, queryset, name, value):
return queryset.filter(attachment_cover__isnull=(not value))
def filter_has_release_date(self, queryset, name, value):
return queryset.filter(release_date__isnull=(not value))
class LibraryFilter(filters.FilterSet):
q = fields.SearchFilter(
search_fields=["name"],
)
scope = common_filters.ActorScopeFilter(
actor_field="actor",
distinct=True,
library_field="pk",
)
class Meta:
model = models.Library
fields = ["privacy_level"]