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"]