Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
funkwhale
Manage
Activity
Members
Labels
Plan
Issues
0
Issue boards
Milestones
Wiki
Code
Merge requests
0
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container Registry
Model registry
Operate
Environments
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Paul Walko
funkwhale
Commits
47aa209d
Verified
Commit
47aa209d
authored
6 years ago
by
Eliot Berriot
Browse files
Options
Downloads
Patches
Plain Diff
See !368: ensure we filter playable entities in subsonic API
parent
224fa4bf
No related branches found
Branches containing commit
No related tags found
Tags containing commit
No related merge requests found
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
api/funkwhale_api/music/factories.py
+22
-0
22 additions, 0 deletions
api/funkwhale_api/music/factories.py
api/funkwhale_api/subsonic/views.py
+26
-8
26 additions, 8 deletions
api/funkwhale_api/subsonic/views.py
api/tests/subsonic/test_views.py
+64
-27
64 additions, 27 deletions
api/tests/subsonic/test_views.py
with
112 additions
and
35 deletions
api/funkwhale_api/music/factories.py
+
22
−
0
View file @
47aa209d
...
...
@@ -14,11 +14,28 @@ SAMPLES_PATH = os.path.join(
)
def
playable_factory
(
field
):
@factory.post_generation
def
inner
(
self
,
create
,
extracted
,
**
kwargs
):
if
not
create
:
return
if
extracted
:
UploadFactory
(
library__privacy_level
=
"
everyone
"
,
import_status
=
"
finished
"
,
**
{
field
:
self
}
)
return
inner
@registry.register
class
ArtistFactory
(
factory
.
django
.
DjangoModelFactory
):
name
=
factory
.
Faker
(
"
name
"
)
mbid
=
factory
.
Faker
(
"
uuid4
"
)
fid
=
factory
.
Faker
(
"
federation_url
"
)
playable
=
playable_factory
(
"
track__album__artist
"
)
class
Meta
:
model
=
"
music.Artist
"
...
...
@@ -33,6 +50,7 @@ class AlbumFactory(factory.django.DjangoModelFactory):
artist
=
factory
.
SubFactory
(
ArtistFactory
)
release_group_id
=
factory
.
Faker
(
"
uuid4
"
)
fid
=
factory
.
Faker
(
"
federation_url
"
)
playable
=
playable_factory
(
"
track__album
"
)
class
Meta
:
model
=
"
music.Album
"
...
...
@@ -47,6 +65,7 @@ class TrackFactory(factory.django.DjangoModelFactory):
artist
=
factory
.
SelfAttribute
(
"
album.artist
"
)
position
=
1
tags
=
ManyToManyFromList
(
"
tags
"
)
playable
=
playable_factory
(
"
track
"
)
class
Meta
:
model
=
"
music.Track
"
...
...
@@ -71,6 +90,9 @@ class UploadFactory(factory.django.DjangoModelFactory):
class
Params
:
in_place
=
factory
.
Trait
(
audio_file
=
None
)
playable
=
factory
.
Trait
(
import_status
=
"
finished
"
,
library__privacy_level
=
"
everyone
"
)
@registry.register
...
...
This diff is collapsed.
Click to expand it.
api/funkwhale_api/subsonic/views.py
+
26
−
8
View file @
47aa209d
...
...
@@ -19,7 +19,9 @@ from funkwhale_api.playlists import models as playlists_models
from
.
import
authentication
,
filters
,
negotiation
,
serializers
def
find_object
(
queryset
,
model_field
=
"
pk
"
,
field
=
"
id
"
,
cast
=
int
):
def
find_object
(
queryset
,
model_field
=
"
pk
"
,
field
=
"
id
"
,
cast
=
int
,
filter_playable
=
False
):
def
decorator
(
func
):
def
inner
(
self
,
request
,
*
args
,
**
kwargs
):
data
=
request
.
GET
or
request
.
POST
...
...
@@ -50,6 +52,11 @@ def find_object(queryset, model_field="pk", field="id", cast=int):
qs
=
queryset
if
hasattr
(
qs
,
"
__call__
"
):
qs
=
qs
(
request
)
if
filter_playable
:
actor
=
utils
.
get_actor_from_request
(
request
)
qs
=
qs
.
playable_by
(
actor
).
distinct
()
try
:
obj
=
qs
.
get
(
**
{
model_field
:
value
})
except
qs
.
model
.
DoesNotExist
:
...
...
@@ -124,7 +131,9 @@ class SubsonicViewSet(viewsets.GenericViewSet):
@list_route
(
methods
=
[
"
get
"
,
"
post
"
],
url_name
=
"
get_artists
"
,
url_path
=
"
getArtists
"
)
def
get_artists
(
self
,
request
,
*
args
,
**
kwargs
):
artists
=
music_models
.
Artist
.
objects
.
all
()
artists
=
music_models
.
Artist
.
objects
.
all
().
playable_by
(
utils
.
get_actor_from_request
(
request
)
)
data
=
serializers
.
GetArtistsSerializer
(
artists
).
data
payload
=
{
"
artists
"
:
data
}
...
...
@@ -132,14 +141,16 @@ class SubsonicViewSet(viewsets.GenericViewSet):
@list_route
(
methods
=
[
"
get
"
,
"
post
"
],
url_name
=
"
get_indexes
"
,
url_path
=
"
getIndexes
"
)
def
get_indexes
(
self
,
request
,
*
args
,
**
kwargs
):
artists
=
music_models
.
Artist
.
objects
.
all
()
artists
=
music_models
.
Artist
.
objects
.
all
().
playable_by
(
utils
.
get_actor_from_request
(
request
)
)
data
=
serializers
.
GetArtistsSerializer
(
artists
).
data
payload
=
{
"
indexes
"
:
data
}
return
response
.
Response
(
payload
,
status
=
200
)
@list_route
(
methods
=
[
"
get
"
,
"
post
"
],
url_name
=
"
get_artist
"
,
url_path
=
"
getArtist
"
)
@find_object
(
music_models
.
Artist
.
objects
.
all
())
@find_object
(
music_models
.
Artist
.
objects
.
all
()
,
filter_playable
=
True
)
def
get_artist
(
self
,
request
,
*
args
,
**
kwargs
):
artist
=
kwargs
.
pop
(
"
obj
"
)
data
=
serializers
.
GetArtistSerializer
(
artist
).
data
...
...
@@ -148,7 +159,7 @@ class SubsonicViewSet(viewsets.GenericViewSet):
return
response
.
Response
(
payload
,
status
=
200
)
@list_route
(
methods
=
[
"
get
"
,
"
post
"
],
url_name
=
"
get_song
"
,
url_path
=
"
getSong
"
)
@find_object
(
music_models
.
Track
.
objects
.
all
())
@find_object
(
music_models
.
Track
.
objects
.
all
()
,
filter_playable
=
True
)
def
get_song
(
self
,
request
,
*
args
,
**
kwargs
):
track
=
kwargs
.
pop
(
"
obj
"
)
data
=
serializers
.
GetSongSerializer
(
track
).
data
...
...
@@ -159,14 +170,16 @@ class SubsonicViewSet(viewsets.GenericViewSet):
@list_route
(
methods
=
[
"
get
"
,
"
post
"
],
url_name
=
"
get_artist_info2
"
,
url_path
=
"
getArtistInfo2
"
)
@find_object
(
music_models
.
Artist
.
objects
.
all
())
@find_object
(
music_models
.
Artist
.
objects
.
all
()
,
filter_playable
=
True
)
def
get_artist_info2
(
self
,
request
,
*
args
,
**
kwargs
):
payload
=
{
"
artist-info2
"
:
{}}
return
response
.
Response
(
payload
,
status
=
200
)
@list_route
(
methods
=
[
"
get
"
,
"
post
"
],
url_name
=
"
get_album
"
,
url_path
=
"
getAlbum
"
)
@find_object
(
music_models
.
Album
.
objects
.
select_related
(
"
artist
"
))
@find_object
(
music_models
.
Album
.
objects
.
select_related
(
"
artist
"
),
filter_playable
=
True
)
def
get_album
(
self
,
request
,
*
args
,
**
kwargs
):
album
=
kwargs
.
pop
(
"
obj
"
)
data
=
serializers
.
GetAlbumSerializer
(
album
).
data
...
...
@@ -174,7 +187,7 @@ class SubsonicViewSet(viewsets.GenericViewSet):
return
response
.
Response
(
payload
,
status
=
200
)
@list_route
(
methods
=
[
"
get
"
,
"
post
"
],
url_name
=
"
stream
"
,
url_path
=
"
stream
"
)
@find_object
(
music_models
.
Track
.
objects
.
all
())
@find_object
(
music_models
.
Track
.
objects
.
all
()
,
filter_playable
=
True
)
def
stream
(
self
,
request
,
*
args
,
**
kwargs
):
track
=
kwargs
.
pop
(
"
obj
"
)
queryset
=
track
.
uploads
.
select_related
(
"
track__album__artist
"
,
"
track__artist
"
)
...
...
@@ -221,6 +234,9 @@ class SubsonicViewSet(viewsets.GenericViewSet):
data
=
request
.
GET
or
request
.
POST
filterset
=
filters
.
AlbumList2FilterSet
(
data
,
queryset
=
queryset
)
queryset
=
filterset
.
qs
actor
=
utils
.
get_actor_from_request
(
request
)
queryset
=
queryset
.
playable_by
(
actor
)
try
:
offset
=
int
(
data
[
"
offset
"
])
except
(
TypeError
,
KeyError
,
ValueError
):
...
...
@@ -240,6 +256,7 @@ class SubsonicViewSet(viewsets.GenericViewSet):
def
search3
(
self
,
request
,
*
args
,
**
kwargs
):
data
=
request
.
GET
or
request
.
POST
query
=
str
(
data
.
get
(
"
query
"
,
""
)).
replace
(
"
*
"
,
""
)
actor
=
utils
.
get_actor_from_request
(
request
)
conf
=
[
{
"
subsonic
"
:
"
artist
"
,
...
...
@@ -292,6 +309,7 @@ class SubsonicViewSet(viewsets.GenericViewSet):
queryset
=
c
[
"
queryset
"
].
filter
(
utils
.
get_query
(
query
,
c
[
"
search_fields
"
])
)
queryset
=
queryset
.
playable_by
(
actor
)
queryset
=
queryset
[
offset
:
offset
+
size
]
payload
[
"
searchResult3
"
][
c
[
"
subsonic
"
]]
=
c
[
"
serializer
"
](
queryset
)
return
response
.
Response
(
payload
)
...
...
This diff is collapsed.
Click to expand it.
api/tests/subsonic/test_views.py
+
64
−
27
View file @
47aa209d
...
...
@@ -74,10 +74,13 @@ def test_ping(f, db, api_client):
@pytest.mark.parametrize
(
"
f
"
,
[
"
xml
"
,
"
json
"
])
def
test_get_artists
(
f
,
db
,
logged_in_api_client
,
factories
):
def
test_get_artists
(
f
,
db
,
logged_in_api_client
,
factories
,
mocker
,
queryset_equal_queries
):
url
=
reverse
(
"
api:subsonic-get-artists
"
)
assert
url
.
endswith
(
"
getArtists
"
)
is
True
factories
[
"
music.Artist
"
].
create_batch
(
size
=
10
)
factories
[
"
music.Artist
"
].
create_batch
(
size
=
3
,
playable
=
True
)
playable_by
=
mocker
.
spy
(
music_models
.
ArtistQuerySet
,
"
playable_by
"
)
expected
=
{
"
artists
"
:
serializers
.
GetArtistsSerializer
(
music_models
.
Artist
.
objects
.
all
()
...
...
@@ -87,19 +90,25 @@ def test_get_artists(f, db, logged_in_api_client, factories):
assert
response
.
status_code
==
200
assert
response
.
data
==
expected
playable_by
.
assert_called_once_with
(
music_models
.
Artist
.
objects
.
all
(),
None
)
@pytest.mark.parametrize
(
"
f
"
,
[
"
xml
"
,
"
json
"
])
def
test_get_artist
(
f
,
db
,
logged_in_api_client
,
factories
):
def
test_get_artist
(
f
,
db
,
logged_in_api_client
,
factories
,
mocker
,
queryset_equal_queries
):
url
=
reverse
(
"
api:subsonic-get-artist
"
)
assert
url
.
endswith
(
"
getArtist
"
)
is
True
artist
=
factories
[
"
music.Artist
"
]()
factories
[
"
music.Album
"
].
create_batch
(
size
=
3
,
artist
=
artist
)
artist
=
factories
[
"
music.Artist
"
](
playable
=
True
)
factories
[
"
music.Album
"
].
create_batch
(
size
=
3
,
artist
=
artist
,
playable
=
True
)
playable_by
=
mocker
.
spy
(
music_models
.
ArtistQuerySet
,
"
playable_by
"
)
expected
=
{
"
artist
"
:
serializers
.
GetArtistSerializer
(
artist
).
data
}
response
=
logged_in_api_client
.
get
(
url
,
{
"
id
"
:
artist
.
pk
})
assert
response
.
status_code
==
200
assert
response
.
data
==
expected
playable_by
.
assert_called_once_with
(
music_models
.
Artist
.
objects
.
all
(),
None
)
@pytest.mark.parametrize
(
"
f
"
,
[
"
xml
"
,
"
json
"
])
...
...
@@ -114,10 +123,13 @@ def test_get_invalid_artist(f, db, logged_in_api_client, factories):
@pytest.mark.parametrize
(
"
f
"
,
[
"
xml
"
,
"
json
"
])
def
test_get_artist_info2
(
f
,
db
,
logged_in_api_client
,
factories
):
def
test_get_artist_info2
(
f
,
db
,
logged_in_api_client
,
factories
,
mocker
,
queryset_equal_queries
):
url
=
reverse
(
"
api:subsonic-get-artist-info2
"
)
assert
url
.
endswith
(
"
getArtistInfo2
"
)
is
True
artist
=
factories
[
"
music.Artist
"
]()
artist
=
factories
[
"
music.Artist
"
](
playable
=
True
)
playable_by
=
mocker
.
spy
(
music_models
.
ArtistQuerySet
,
"
playable_by
"
)
expected
=
{
"
artist-info2
"
:
{}}
response
=
logged_in_api_client
.
get
(
url
,
{
"
id
"
:
artist
.
pk
})
...
...
@@ -125,50 +137,62 @@ def test_get_artist_info2(f, db, logged_in_api_client, factories):
assert
response
.
status_code
==
200
assert
response
.
data
==
expected
playable_by
.
assert_called_once_with
(
music_models
.
Artist
.
objects
.
all
(),
None
)
@pytest.mark.parametrize
(
"
f
"
,
[
"
xml
"
,
"
json
"
])
def
test_get_album
(
f
,
db
,
logged_in_api_client
,
factories
):
def
test_get_album
(
f
,
db
,
logged_in_api_client
,
factories
,
mocker
,
queryset_equal_queries
):
url
=
reverse
(
"
api:subsonic-get-album
"
)
assert
url
.
endswith
(
"
getAlbum
"
)
is
True
artist
=
factories
[
"
music.Artist
"
]()
album
=
factories
[
"
music.Album
"
](
artist
=
artist
)
factories
[
"
music.Track
"
].
create_batch
(
size
=
3
,
album
=
album
)
factories
[
"
music.Track
"
].
create_batch
(
size
=
3
,
album
=
album
,
playable
=
True
)
playable_by
=
mocker
.
spy
(
music_models
.
AlbumQuerySet
,
"
playable_by
"
)
expected
=
{
"
album
"
:
serializers
.
GetAlbumSerializer
(
album
).
data
}
response
=
logged_in_api_client
.
get
(
url
,
{
"
f
"
:
f
,
"
id
"
:
album
.
pk
})
assert
response
.
status_code
==
200
assert
response
.
data
==
expected
playable_by
.
assert_called_once_with
(
music_models
.
Album
.
objects
.
select_related
(
"
artist
"
),
None
)
@pytest.mark.parametrize
(
"
f
"
,
[
"
xml
"
,
"
json
"
])
def
test_get_song
(
f
,
db
,
logged_in_api_client
,
factories
):
def
test_get_song
(
f
,
db
,
logged_in_api_client
,
factories
,
mocker
,
queryset_equal_queries
):
url
=
reverse
(
"
api:subsonic-get-song
"
)
assert
url
.
endswith
(
"
getSong
"
)
is
True
artist
=
factories
[
"
music.Artist
"
]()
album
=
factories
[
"
music.Album
"
](
artist
=
artist
)
track
=
factories
[
"
music.Track
"
](
album
=
album
)
track
=
factories
[
"
music.Track
"
](
album
=
album
,
playable
=
True
)
upload
=
factories
[
"
music.Upload
"
](
track
=
track
)
playable_by
=
mocker
.
spy
(
music_models
.
TrackQuerySet
,
"
playable_by
"
)
response
=
logged_in_api_client
.
get
(
url
,
{
"
f
"
:
f
,
"
id
"
:
track
.
pk
})
assert
response
.
status_code
==
200
assert
response
.
data
==
{
"
song
"
:
serializers
.
get_track_data
(
track
.
album
,
track
,
upload
)
}
playable_by
.
assert_called_once_with
(
music_models
.
Track
.
objects
.
all
(),
None
)
@pytest.mark.parametrize
(
"
f
"
,
[
"
xml
"
,
"
json
"
])
def
test_stream
(
f
,
db
,
logged_in_api_client
,
factories
,
mocker
):
def
test_stream
(
f
,
db
,
logged_in_api_client
,
factories
,
mocker
,
queryset_equal_queries
):
url
=
reverse
(
"
api:subsonic-stream
"
)
mocked_serve
=
mocker
.
spy
(
music_views
,
"
handle_serve
"
)
assert
url
.
endswith
(
"
stream
"
)
is
True
artist
=
factories
[
"
music.Artist
"
]()
album
=
factories
[
"
music.Album
"
](
artist
=
artist
)
track
=
factories
[
"
music.Track
"
](
album
=
album
)
upload
=
factories
[
"
music.Upload
"
](
track
=
track
)
response
=
logged_in_api_client
.
get
(
url
,
{
"
f
"
:
f
,
"
id
"
:
track
.
pk
})
upload
=
factories
[
"
music.Upload
"
](
playable
=
True
)
playable_by
=
mocker
.
spy
(
music_models
.
TrackQuerySet
,
"
playable_by
"
)
response
=
logged_in_api_client
.
get
(
url
,
{
"
f
"
:
f
,
"
id
"
:
upload
.
track
.
pk
})
mocked_serve
.
assert_called_once_with
(
upload
=
upload
,
user
=
logged_in_api_client
.
user
)
assert
response
.
status_code
==
200
playable_by
.
assert_called_once_with
(
music_models
.
Track
.
objects
.
all
(),
None
)
@pytest.mark.parametrize
(
"
f
"
,
[
"
xml
"
,
"
json
"
])
...
...
@@ -231,25 +255,30 @@ def test_get_starred(f, db, logged_in_api_client, factories):
@pytest.mark.parametrize
(
"
f
"
,
[
"
xml
"
,
"
json
"
])
def
test_get_album_list2
(
f
,
db
,
logged_in_api_client
,
factories
):
def
test_get_album_list2
(
f
,
db
,
logged_in_api_client
,
factories
,
mocker
,
queryset_equal_queries
):
url
=
reverse
(
"
api:subsonic-get-album-list2
"
)
assert
url
.
endswith
(
"
getAlbumList2
"
)
is
True
album1
=
factories
[
"
music.Album
"
]()
album2
=
factories
[
"
music.Album
"
]()
album1
=
factories
[
"
music.Album
"
](
playable
=
True
)
album2
=
factories
[
"
music.Album
"
](
playable
=
True
)
factories
[
"
music.Album
"
]()
playable_by
=
mocker
.
spy
(
music_models
.
AlbumQuerySet
,
"
playable_by
"
)
response
=
logged_in_api_client
.
get
(
url
,
{
"
f
"
:
f
,
"
type
"
:
"
newest
"
})
assert
response
.
status_code
==
200
assert
response
.
data
==
{
"
albumList2
"
:
{
"
album
"
:
serializers
.
get_album_list2_data
([
album2
,
album1
])}
}
playable_by
.
assert_called_once
()
@pytest.mark.parametrize
(
"
f
"
,
[
"
xml
"
,
"
json
"
])
def
test_get_album_list2_pagination
(
f
,
db
,
logged_in_api_client
,
factories
):
url
=
reverse
(
"
api:subsonic-get-album-list2
"
)
assert
url
.
endswith
(
"
getAlbumList2
"
)
is
True
album1
=
factories
[
"
music.Album
"
]()
factories
[
"
music.Album
"
]()
album1
=
factories
[
"
music.Album
"
](
playable
=
True
)
factories
[
"
music.Album
"
](
playable
=
True
)
response
=
logged_in_api_client
.
get
(
url
,
{
"
f
"
:
f
,
"
type
"
:
"
newest
"
,
"
size
"
:
1
,
"
offset
"
:
1
}
)
...
...
@@ -264,12 +293,15 @@ def test_get_album_list2_pagination(f, db, logged_in_api_client, factories):
def
test_search3
(
f
,
db
,
logged_in_api_client
,
factories
):
url
=
reverse
(
"
api:subsonic-search3
"
)
assert
url
.
endswith
(
"
search3
"
)
is
True
artist
=
factories
[
"
music.Artist
"
](
name
=
"
testvalue
"
)
artist
=
factories
[
"
music.Artist
"
](
name
=
"
testvalue
"
,
playable
=
True
)
factories
[
"
music.Artist
"
](
name
=
"
nope
"
)
album
=
factories
[
"
music.Album
"
](
title
=
"
testvalue
"
)
factories
[
"
music.Artist
"
](
name
=
"
nope2
"
,
playable
=
True
)
album
=
factories
[
"
music.Album
"
](
title
=
"
testvalue
"
,
playable
=
True
)
factories
[
"
music.Album
"
](
title
=
"
nope
"
)
track
=
factories
[
"
music.Track
"
](
title
=
"
testvalue
"
)
factories
[
"
music.Album
"
](
title
=
"
nope2
"
,
playable
=
True
)
track
=
factories
[
"
music.Track
"
](
title
=
"
testvalue
"
,
playable
=
True
)
factories
[
"
music.Track
"
](
title
=
"
nope
"
)
factories
[
"
music.Track
"
](
title
=
"
nope2
"
,
playable
=
True
)
response
=
logged_in_api_client
.
get
(
url
,
{
"
f
"
:
f
,
"
query
"
:
"
testval
"
})
...
...
@@ -385,20 +417,25 @@ def test_get_music_folders(f, db, logged_in_api_client, factories):
@pytest.mark.parametrize
(
"
f
"
,
[
"
xml
"
,
"
json
"
])
def
test_get_indexes
(
f
,
db
,
logged_in_api_client
,
factories
):
def
test_get_indexes
(
f
,
db
,
logged_in_api_client
,
factories
,
mocker
,
queryset_equal_queries
):
url
=
reverse
(
"
api:subsonic-get-indexes
"
)
assert
url
.
endswith
(
"
getIndexes
"
)
is
True
factories
[
"
music.Artist
"
].
create_batch
(
size
=
10
)
factories
[
"
music.Artist
"
].
create_batch
(
size
=
3
,
playable
=
True
)
expected
=
{
"
indexes
"
:
serializers
.
GetArtistsSerializer
(
music_models
.
Artist
.
objects
.
all
()
).
data
}
playable_by
=
mocker
.
spy
(
music_models
.
ArtistQuerySet
,
"
playable_by
"
)
response
=
logged_in_api_client
.
get
(
url
)
assert
response
.
status_code
==
200
assert
response
.
data
==
expected
playable_by
.
assert_called_once_with
(
music_models
.
Artist
.
objects
.
all
(),
None
)
def
test_get_cover_art_album
(
factories
,
logged_in_api_client
):
url
=
reverse
(
"
api:subsonic-get-cover-art
"
)
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment