Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
interfect
funkwhale
Commits
1aa9b557
Commit
1aa9b557
authored
Oct 01, 2018
by
Eliot Berriot
Browse files
Merge branch 'subsonic-user-libraries' into 'develop'
Subsonic user libraries See merge request
funkwhale/funkwhale!428
parents
96360a39
47aa209d
Changes
4
Hide whitespace changes
Inline
Side-by-side
api/funkwhale_api/music/factories.py
View file @
1aa9b557
...
...
@@ -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
...
...
api/funkwhale_api/subsonic/views.py
View file @
1aa9b557
...
...
@@ -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
)
...
...
api/requirements/base.txt
View file @
1aa9b557
...
...
@@ -49,7 +49,7 @@ mutagen>=1.39,<1.40
# Until this is merged
django-taggit>=0.22,<0.23
# Until this is merged
git+https://github.com/EliotBerriot/PyMemoize.git@django
pymemoize==1.0.3
django-dynamic-preferences>=1.5,<1.6
pyacoustid>=1.1.5,<1.2
...
...
api/tests/subsonic/test_views.py
View file @
1aa9b557
...
...
@@ -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"
)
...
...
Write
Preview
Supports
Markdown
0%
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!
Cancel
Please
register
or
sign in
to comment