From ae69cd9d5e864a42446c189219bea304a6ef4e83 Mon Sep 17 00:00:00 2001 From: Agate Date: Fri, 5 Jun 2020 05:30:29 +0200 Subject: [PATCH 1/4] Fix #1117: wording issue on artist channel page --- changes/changelog.d/1117.bugfix | 1 + front/src/views/channels/DetailBase.vue | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 changes/changelog.d/1117.bugfix diff --git a/changes/changelog.d/1117.bugfix b/changes/changelog.d/1117.bugfix new file mode 100644 index 000000000..b2236ee70 --- /dev/null +++ b/changes/changelog.d/1117.bugfix @@ -0,0 +1 @@ +Fixed a wording issue on artist channel page (#1117) diff --git a/front/src/views/channels/DetailBase.vue b/front/src/views/channels/DetailBase.vue index 19ce17cab..06491313f 100644 --- a/front/src/views/channels/DetailBase.vue +++ b/front/src/views/channels/DetailBase.vue @@ -18,11 +18,14 @@ @@ -183,6 +189,7 @@ import _ from '@/lodash' import {mapState} from 'vuex' import showdown from 'showdown' import AlbumWidget from "@/components/audio/album/Widget" +import ChannelsWidget from "@/components/audio/ChannelsWidget" import LoginForm from "@/components/auth/LoginForm" import SignupForm from "@/components/auth/SignupForm" import {humanSize } from '@/filters' @@ -190,6 +197,7 @@ import {humanSize } from '@/filters' export default { components: { AlbumWidget, + ChannelsWidget, LoginForm, SignupForm, }, From f54038ca837e98e264b97685d1c3fbc98e53a0ad Mon Sep 17 00:00:00 2001 From: Agate Date: Fri, 5 Jun 2020 10:42:56 +0200 Subject: [PATCH 3/4] Resolve "CLI in-place import impossible with virtualenv with python3.5" --- .../music/management/commands/import_files.py | 6 +++++- api/tests/files/nested/valid.ogg | Bin 0 -> 5158 bytes api/tests/test_import_audio_file.py | 14 ++++++++++++++ changes/changelog.d/1048.bugfix | 1 + 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 api/tests/files/nested/valid.ogg create mode 100644 changes/changelog.d/1048.bugfix diff --git a/api/funkwhale_api/music/management/commands/import_files.py b/api/funkwhale_api/music/management/commands/import_files.py index fab980510..ddc598d06 100644 --- a/api/funkwhale_api/music/management/commands/import_files.py +++ b/api/funkwhale_api/music/management/commands/import_files.py @@ -27,7 +27,8 @@ def crawl_dir(dir, extensions, recursive=True, ignored=[]): if os.path.isfile(dir): yield dir return - with os.scandir(dir) as scanner: + try: + scanner = os.scandir(dir) for entry in scanner: if entry.is_file(): for e in extensions: @@ -38,6 +39,9 @@ def crawl_dir(dir, extensions, recursive=True, ignored=[]): yield from crawl_dir( entry, extensions, recursive=recursive, ignored=ignored ) + finally: + if hasattr(scanner, "close"): + scanner.close() def batch(iterable, n=1): diff --git a/api/tests/files/nested/valid.ogg b/api/tests/files/nested/valid.ogg new file mode 100644 index 0000000000000000000000000000000000000000..e1643848a07a548e55d53726d4e4fe168a8ab2cd GIT binary patch literal 5158 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 0 + assert "Successfully imported {} new tracks".format(imported) in captured.out + assert "For details, please refer to import reference" in captured.out diff --git a/changes/changelog.d/1048.bugfix b/changes/changelog.d/1048.bugfix new file mode 100644 index 000000000..0f1973444 --- /dev/null +++ b/changes/changelog.d/1048.bugfix @@ -0,0 +1 @@ +Fixed recursive CLI importing crashing under Python 3.5 (#1148, #1147) From aa8b1b5f83c3394561593acdd9fc6cc90543b237 Mon Sep 17 00:00:00 2001 From: Agate Date: Fri, 5 Jun 2020 11:21:10 +0200 Subject: [PATCH 4/4] Fix #1151: Updated the /api/v1/libraries endpoint to support listing public libraries of a pod --- api/funkwhale_api/music/filters.py | 9 +++++++++ api/funkwhale_api/music/views.py | 7 ++++++- api/tests/music/test_views.py | 17 +++++++++++++++-- changes/changelog.d/1151.enhancement | 1 + changes/notes.rst | 13 +++++++++++++ front/src/views/content/libraries/Home.vue | 2 +- 6 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 changes/changelog.d/1151.enhancement diff --git a/api/funkwhale_api/music/filters.py b/api/funkwhale_api/music/filters.py index 79b2d2c63..d69dd13a3 100644 --- a/api/funkwhale_api/music/filters.py +++ b/api/funkwhale_api/music/filters.py @@ -218,3 +218,12 @@ class AlbumFilter( def filter_playable(self, queryset, name, value): actor = utils.get_actor_from_request(self.request) return queryset.playable_by(actor, value) + + +class LibraryFilter(filters.FilterSet): + q = fields.SearchFilter(search_fields=["name"],) + scope = common_filters.ActorScopeFilter(actor_field="actor", distinct=True) + + class Meta: + model = models.Library + fields = ["privacy_level", "q", "scope"] diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py index 93635c11b..6c9f7e41c 100644 --- a/api/funkwhale_api/music/views.py +++ b/api/funkwhale_api/music/views.py @@ -273,6 +273,7 @@ class LibraryViewSet( oauth_permissions.ScopePermission, common_permissions.OwnerPermission, ] + filterset_class = filters.LibraryFilter required_scope = "libraries" anonymous_policy = "setting" owner_field = "actor.user" @@ -282,8 +283,12 @@ class LibraryViewSet( qs = super().get_queryset() # allow retrieving a single library by uuid if request.user isn't # the owner. Any other get should be from the owner only - if self.action != "retrieve": + if self.action not in ["retrieve", "list"]: qs = qs.filter(actor=self.request.user.actor) + if self.action == "list": + actor = utils.get_actor_from_request(self.request) + qs = qs.viewable_by(actor) + return qs def perform_create(self, serializer): diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py index 2a362a536..66f80d8fe 100644 --- a/api/tests/music/test_views.py +++ b/api/tests/music/test_views.py @@ -631,10 +631,10 @@ def test_user_can_create_library(factories, logged_in_api_client): def test_user_can_list_their_library(factories, logged_in_api_client): actor = logged_in_api_client.user.create_actor() library = factories["music.Library"](actor=actor) - factories["music.Library"]() + factories["music.Library"](privacy_level="everyone") url = reverse("api:v1:libraries-list") - response = logged_in_api_client.get(url) + response = logged_in_api_client.get(url, {"scope": "me"}) assert response.status_code == 200 assert response.data["count"] == 1 @@ -651,6 +651,19 @@ def test_user_can_retrieve_another_user_library(factories, logged_in_api_client) assert response.data["uuid"] == str(library.uuid) +def test_user_can_list_public_libraries(factories, api_client, preferences): + preferences["common__api_authentication_required"] = False + library = factories["music.Library"](privacy_level="everyone") + factories["music.Library"](privacy_level="me") + + url = reverse("api:v1:libraries-list") + response = api_client.get(url) + + assert response.status_code == 200 + assert response.data["count"] == 1 + assert response.data["results"][0]["uuid"] == str(library.uuid) + + def test_library_list_excludes_channel_library(factories, logged_in_api_client): actor = logged_in_api_client.user.create_actor() factories["audio.Channel"](attributed_to=actor) diff --git a/changes/changelog.d/1151.enhancement b/changes/changelog.d/1151.enhancement new file mode 100644 index 000000000..c9b867a30 --- /dev/null +++ b/changes/changelog.d/1151.enhancement @@ -0,0 +1 @@ +Updated the /api/v1/libraries endpoint to support listing public libraries from other users/pods (#1151) diff --git a/changes/notes.rst b/changes/notes.rst index 96ac3d765..3dc81fda8 100644 --- a/changes/notes.rst +++ b/changes/notes.rst @@ -5,3 +5,16 @@ Next release notes Those release notes refer to the current development branch and are reset after each release. + +Small API breaking change in ``/api/v1/libraries`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To allow easier crawling of public libraries on a pod,we had to make a slight breaking change +to the behaviour of ``GET /api/v1/libraries``. + +Before, it returned only libraries owned by the current user. + +Now, it returns all the accessible libraries (including ones from other users and pods). + +If you are consuming the API via a third-party client and need to retrieve your libraries, +use the ``scope`` parameter, like this: ``GET /api/v1/libraries?scope=me`` diff --git a/front/src/views/content/libraries/Home.vue b/front/src/views/content/libraries/Home.vue index 2e5e39498..e3e71995e 100644 --- a/front/src/views/content/libraries/Home.vue +++ b/front/src/views/content/libraries/Home.vue @@ -53,7 +53,7 @@ export default { fetch() { this.isLoading = true let self = this - axios.get("libraries/").then(response => { + axios.get("libraries/", {params: {scope: 'me'}}).then(response => { self.isLoading = false self.libraries = response.data.results if (self.libraries.length === 0) {