diff --git a/api/funkwhale_api/music/filters.py b/api/funkwhale_api/music/filters.py index ff937a0f5c3aac8d5609b188d18b796e3b809e3a..fbea3735a267188485bfe236ecbd39ae56ae2e39 100644 --- a/api/funkwhale_api/music/filters.py +++ b/api/funkwhale_api/music/filters.py @@ -1,12 +1,36 @@ -import django_filters +from django.db.models import Count + +from django_filters import rest_framework as filters from . import models -class ArtistFilter(django_filters.FilterSet): +class ListenableMixin(filters.FilterSet): + listenable = filters.BooleanFilter(name='_', method='filter_listenable') + + def filter_listenable(self, queryset, name, value): + queryset = queryset.annotate( + files_count=Count('tracks__files') + ) + if value: + return queryset.filter(files_count__gt=0) + else: + return queryset.filter(files_count=0) + + +class ArtistFilter(ListenableMixin): class Meta: model = models.Artist fields = { - 'name': ['exact', 'iexact', 'startswith', 'icontains'] + 'name': ['exact', 'iexact', 'startswith', 'icontains'], + 'listenable': 'exact', } + + +class AlbumFilter(ListenableMixin): + listenable = filters.BooleanFilter(name='_', method='filter_listenable') + + class Meta: + model = models.Album + fields = ['listenable'] diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index d026c9847ca3db7f9920469a225399b0d6c0d3dc..78a3588762716759c21ebbf59d265d0bb90bc558 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -54,6 +54,7 @@ class TagViewSetMixin(object): queryset = queryset.filter(tags__pk=tag) return queryset + class ArtistViewSet(SearchMixin, viewsets.ReadOnlyModelViewSet): queryset = ( models.Artist.objects.all() @@ -67,6 +68,7 @@ class ArtistViewSet(SearchMixin, viewsets.ReadOnlyModelViewSet): filter_class = filters.ArtistFilter ordering_fields = ('id', 'name', 'creation_date') + class AlbumViewSet(SearchMixin, viewsets.ReadOnlyModelViewSet): queryset = ( models.Album.objects.all() @@ -78,6 +80,7 @@ class AlbumViewSet(SearchMixin, viewsets.ReadOnlyModelViewSet): permission_classes = [ConditionalAuthentication] search_fields = ['title__unaccent'] ordering_fields = ('creation_date',) + filter_class = filters.AlbumFilter class ImportBatchViewSet( diff --git a/api/tests/conftest.py b/api/tests/conftest.py index 2d655f23f28dd2ea1ad56d4073df1147451c603c..4ff1a8ee789fa0559c129d05ba0e87da723b8795 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -4,6 +4,7 @@ import pytest from django.core.cache import cache as django_cache from dynamic_preferences.registries import global_preferences_registry from rest_framework.test import APIClient +from rest_framework.test import APIRequestFactory from funkwhale_api.activity import record from funkwhale_api.taskapp import celery @@ -84,6 +85,11 @@ def superuser_client(db, factories, client): delattr(client, 'user') +@pytest.fixture +def api_request(): + return APIRequestFactory() + + @pytest.fixture def activity_registry(): r = record.registry diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py new file mode 100644 index 0000000000000000000000000000000000000000..2956046168ca30487976512518bedce919752f2e --- /dev/null +++ b/api/tests/music/test_views.py @@ -0,0 +1,45 @@ +import pytest + +from funkwhale_api.music import views + + +@pytest.mark.parametrize('param,expected', [ + ('true', 'full'), + ('false', 'empty'), +]) +def test_artist_view_filter_listenable( + param, expected, factories, api_request): + artists = { + 'empty': factories['music.Artist'](), + 'full': factories['music.TrackFile']().track.artist, + } + + request = api_request.get('/', {'listenable': param}) + view = views.ArtistViewSet() + view.action_map = {'get': 'list'} + expected = [artists[expected]] + view.request = view.initialize_request(request) + queryset = view.filter_queryset(view.get_queryset()) + + assert list(queryset) == expected + + +@pytest.mark.parametrize('param,expected', [ + ('true', 'full'), + ('false', 'empty'), +]) +def test_album_view_filter_listenable( + param, expected, factories, api_request): + artists = { + 'empty': factories['music.Album'](), + 'full': factories['music.TrackFile']().track.album, + } + + request = api_request.get('/', {'listenable': param}) + view = views.AlbumViewSet() + view.action_map = {'get': 'list'} + expected = [artists[expected]] + view.request = view.initialize_request(request) + queryset = view.filter_queryset(view.get_queryset()) + + assert list(queryset) == expected diff --git a/changes/changelog.d/114.feature b/changes/changelog.d/114.feature new file mode 100644 index 0000000000000000000000000000000000000000..88e3bfc1194f390c2eac430b72cc706872a72338 --- /dev/null +++ b/changes/changelog.d/114.feature @@ -0,0 +1 @@ +Can now filter artists and albums with no listenable tracks (#114) diff --git a/front/src/components/library/Artists.vue b/front/src/components/library/Artists.vue index 3cf123447128d8198853036ab1be5c0b52ebbc0a..52ccbdd7465c654b28eb53c141f50a6e62ca0209 100644 --- a/front/src/components/library/Artists.vue +++ b/front/src/components/library/Artists.vue @@ -129,7 +129,8 @@ export default { page: this.page, page_size: this.paginateBy, name__icontains: this.query, - ordering: this.getOrderingAsString() + ordering: this.getOrderingAsString(), + listenable: 'true' } logger.default.debug('Fetching artists') axios.get(url, {params: params}).then((response) => {