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 zcmeGgZB$c7c7h)SR2tcc0iz~LG>?iBl-3}gLO>u-q4+?+=SL-hN(590wpH9MHo}0{LLAk&$_$O$)nBh@#ju~YY0~EQct%dWSE!!w4Rw=T52MFYX=qSnJsD+ZJ z1mg59FA5}4Q6joD3~UU9FH4@aV%gHnEt@w6!7PEul4oYYw4`9m z<^m40vO=~h&9@-PD^_ePVMO}9{ceHZE$|z*z_`)mGfS43%|h`A@noEcadxF4wrh&8 zI`lx3tQ&Jrmt7FHgeUHab;Y=O%9M_Lo$Tzc3&vH5*Zln94j-fQALH5@knY7%XpN zP}u~Ai;LlRli^$)bE0pQ&PVJUsu}h^XtTr{qCU!Sfhm7+TBO|P$fcRfo@_-J2&l{ z@biC|Q*+Q*?J2$DcRRGY9@Z9e0BjM~%H5}PVOU1bk*x@q; z=y+P7<;Z{r2FywksaUI7p~KvKb&td?&`gXTX{t$&J!>_k$99Le7>FXw!OdLJej`L_ zcMMq6e@?)Z)e;x)U&Cyl89-sq_|SYK2&m=yn3e%Q{-|1jWC|&N84;dR7M@}XKWmyN zuZcVN7pAB6C8bSS)xWA_@LtJt_ewtNFZk?U!N>gt>*@+Vb(E|dShsF)+b_FM^~$lR z(r_KD4n5UiO%3!p$H+n)RN1mvcP$nCVGyl0%~92D++NqVz5d|d89owl3{wvgsyyIx zTlng_R#jsgs(!uuH46_T6oAllcPT%mlRQ3)(=7OacYcs zdV5YX<_bT)KElea>8m(sR;-Bq$|f6+`FFgE3+bjyL(#O6m{`$fmUZ#IY}ObhRc{Sm zQJ+6`e+D>jGD?ESfJ@-wD7z$cM4DiUJ3OD5<+V`h_#E1rP4-x|W?a8dosQ2H)ez(a zTY(8L619R${rK?B(}FBx(Mp8&NK!E?m%t@$=0qHGQ_58AhNUcB>Nb~iBD}ovAlyoo z8Ki9%b#SzQtvX0Hrvco6{NbC>e8D({6>ovLQLU4(Co4I!0IVv5IAp2mO_19(CPHp= znuzLJH4z(tZ3`wMFlR2Ii?n7bRgr_e#A}f*%bMy_yD3Gat)C2O;# zQTBcdc~0sySM;=d&FUviY6@6wBgp0U{(ACoOieGTvb&6=o;h4E*+`J{Y|dO#Cvlq9 z`pCfxyQ!jOs8dLkd_2glOmCg>VqMmX$F2OtM26oK1-(9bsFB|woUl{4_+w??) zt5NMabNLeu$9}`I>DE%`<%+MRypPp)>;qTVe7LO3qPfuVptT|@x&Ml$$HCsO=ERQ7 zD!+5~C2G@r`;E2a&7}ijJ3o}NFOpwluAxl*slmoK-#X8LWHOG@*!IL2<) zQZg~Ns#@M<)|e!7X^J4(A_sVFzmkY{TeZPtgy?uMuCh_Rq?mTpk=2+ZBoVV!5#*Ef z@jCL5#F4H1z)bZ>G1!4)_F95G%&##?#5Ro)$1o>B=)up?BL~Ylc!cePEJ9_s_7WHk z0Z@!Nv)1SxyBSk|kWFloIAAIn+#|t4VTTto8o`_)ol9#B_;8ceL=@3h?!sX!L|?a{ zg@BNugEK)YU`1O8$ZAJx9~mif0IPN?m|+#NJPkqykC4C_MUvrqQZE4nhPh05vE5m} zrday(;rOQZ{yJq*`*0ue8s-2*hl|R5-Eq$Xk>SW<5V>o%K+;?SK?a(OoMuwr4q+)4 zz3n@k2Rq&lQ9jXpJQrA5gOzX^Z;ip`AOX?Zr?5Nj@z-o#N{1Bb+lL{-V+MzmY0@bO zMO}q^zd;r%UX(9F9H?Eq_jyWA7;S+os-e^#`B@7jejGwWmWrv(&_+o9TGW6U{-rVt9VQ;pGZ_Q zrCkT*9&}?6P-*}H!DxbVujSw)jC>ef4v1!oi1i9UKCA)6#b^pDav+ndin$+IHiRF% zV5~*)Ga$q#$~FJ6LJzls(03&FB&1@lrX^y`!7IMgpruH?)W3$% z$9RV{V9I)pXK8;nlR_cJyLHqQ7r)r z>Lf0Bq$D_bN{`fEs04&SYh(}{*@y+wB3oF_AheuVZxblX1|^t^{oj;W3{}Ke%m4oa zhzfrBy?Ae3I*wfNZ-a*{3W5EbsBJouDfT~SCi!E8Uf9@pAC2&~hz0b)s z?fr(Utd@$C6N4nzDc)3^-K&8bc7&RE%TqE9-`8KT^IyL~D=3)p^WtadEo3B+a= z8t>1Z?e908H8!XORqkT%511I(d?sNIii=N59f$S?=z6Q>vxTpePtO15bk!f8Zan$9 zHmGFuvBg1mSjd=xCQrWDvnqG(hWx_ft>rK6tOVXnW22o2h0lslN?N3z5;Bz+8a9nT zea1|I@bS+S@$vCKN&$@V@xGr}#%9m-Eg|M77UqGuH#?aXye@0%lz(r}Bw0UZ75cp$ zdV&2z??ct)+##uf%sw%^vgME8UHie2RJwJAXuSLQt7pFd;r{*mcV{PeH+swQsUKWC zpYzlA_ulzxeZ4zx;fDLWm&ZKw-oulf3!?@3ox>Nq3)%GD*Z=enM0HG!&%E;b92PQM zT~H7rjUMQ1FvR_scj}pM`~SGE|G}Q2YdtylTXL?=-ZArK=g^EV<`>_(^s;kT*{qzA zkhnK)y)@V+@4Nm{O7xW-4Agh!U4-80e7HOqp?8Z(Myn0CY0)?<0F9jwCxkM~(Tr8@ z5MRRhn_hgc^I?*A5;}c!$rx1prr+uKZIDx7?>y9>K|ekB z-MR;D7d!6IKYt}7S?EXdT|*~Aw{E?F-Y#iI>vMkmPx# literal 0 HcmV?d00001 diff --git a/api/tests/test_import_audio_file.py b/api/tests/test_import_audio_file.py index 04c06a8f4..7ee1028c8 100644 --- a/api/tests/test_import_audio_file.py +++ b/api/tests/test_import_audio_file.py @@ -352,3 +352,17 @@ def test_handle_modified_update_existing_path_if_found_and_attributed_to( event=event, stdout=stdout, library=library, in_place=True, ) update_track_metadata.assert_not_called() + + +def test_import_files(factories, capsys): + # smoke test to ensure the command run properly + library = factories["music.Library"](actor__local=True) + call_command( + "import_files", str(library.uuid), DATA_DIR, interactive=False, recursive=True + ) + captured = capsys.readouterr() + + imported = library.uploads.filter(import_status="finished").count() + assert imported > 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) {