From 62ca3bd73636da9039b32c65908ac4c900ae515d Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@eliotberriot.com>
Date: Sat, 9 Jun 2018 15:36:16 +0200
Subject: [PATCH] Blacked the code

---
 api/config/api_urls.py                        | 112 +--
 api/config/routing.py                         |  19 +-
 api/config/settings/common.py                 | 398 +++++-----
 api/config/settings/local.py                  |  52 +-
 api/config/settings/production.py             |  76 +-
 api/config/urls.py                            |  36 +-
 api/demo/demo-user.py                         |   6 +-
 api/funkwhale_api/__init__.py                 |   9 +-
 api/funkwhale_api/activity/apps.py            |   3 +-
 api/funkwhale_api/activity/record.py          |  15 +-
 api/funkwhale_api/activity/serializers.py     |   9 +-
 api/funkwhale_api/activity/utils.py           |  41 +-
 api/funkwhale_api/activity/views.py           |   2 +-
 api/funkwhale_api/common/auth.py              |  11 +-
 api/funkwhale_api/common/authentication.py    |  32 +-
 api/funkwhale_api/common/consumers.py         |   2 +-
 .../common/dynamic_preferences_registry.py    |  17 +-
 api/funkwhale_api/common/fields.py            |  27 +-
 .../common/management/commands/script.py      |  51 +-
 .../common/migrations/0001_initial.py         |   4 +-
 api/funkwhale_api/common/pagination.py        |   2 +-
 api/funkwhale_api/common/permissions.py       |  22 +-
 api/funkwhale_api/common/preferences.py       |  10 +-
 .../django_permissions_to_user_permissions.py |  22 +-
 api/funkwhale_api/common/scripts/test.py      |   2 +-
 api/funkwhale_api/common/serializers.py       |  61 +-
 api/funkwhale_api/common/session.py           |   7 +-
 api/funkwhale_api/common/storage.py           |   3 +-
 api/funkwhale_api/common/utils.py             |   8 +-
 .../contrib/sites/migrations/0001_initial.py  |  42 +-
 .../0002_set_site_domain_and_name.py          |  19 +-
 .../migrations/0003_auto_20171214_2205.py     |  21 +-
 api/funkwhale_api/downloader/downloader.py    |  17 +-
 api/funkwhale_api/factories.py                |   2 +-
 api/funkwhale_api/favorites/activities.py     |  15 +-
 api/funkwhale_api/favorites/admin.py          |   7 +-
 api/funkwhale_api/favorites/factories.py      |   2 +-
 .../favorites/migrations/0001_initial.py      |  44 +-
 api/funkwhale_api/favorites/models.py         |  13 +-
 api/funkwhale_api/favorites/serializers.py    |  19 +-
 api/funkwhale_api/favorites/urls.py           |   3 +-
 api/funkwhale_api/favorites/views.py          |  22 +-
 api/funkwhale_api/federation/activity.py      |  95 +--
 api/funkwhale_api/federation/actors.py        | 276 +++----
 api/funkwhale_api/federation/admin.py         |  62 +-
 api/funkwhale_api/federation/api_urls.py      |  10 +-
 .../federation/authentication.py              |  14 +-
 .../dynamic_preferences_registry.py           |  73 +-
 api/funkwhale_api/federation/exceptions.py    |   2 -
 api/funkwhale_api/federation/factories.py     | 162 ++--
 api/funkwhale_api/federation/filters.py       |  72 +-
 api/funkwhale_api/federation/keys.py          |  24 +-
 api/funkwhale_api/federation/library.py       | 117 +--
 .../federation/migrations/0001_initial.py     |  86 +-
 .../migrations/0002_auto_20180403_1620.py     |   9 +-
 .../migrations/0003_auto_20180407_1010.py     | 192 +++--
 .../migrations/0004_auto_20180410_2025.py     |  34 +-
 .../migrations/0005_auto_20180413_1723.py     |  24 +-
 .../migrations/0006_auto_20180521_1702.py     |  16 +-
 api/funkwhale_api/federation/models.py        | 121 ++-
 api/funkwhale_api/federation/parsers.py       |   2 +-
 api/funkwhale_api/federation/permissions.py   |  10 +-
 api/funkwhale_api/federation/renderers.py     |   4 +-
 api/funkwhale_api/federation/serializers.py   | 662 +++++++--------
 api/funkwhale_api/federation/signing.py       |  35 +-
 api/funkwhale_api/federation/tasks.py         |  81 +-
 api/funkwhale_api/federation/urls.py          |  18 +-
 api/funkwhale_api/federation/utils.py         |  17 +-
 api/funkwhale_api/federation/views.py         | 239 +++---
 api/funkwhale_api/federation/webfinger.py     |  29 +-
 api/funkwhale_api/history/activities.py       |  15 +-
 api/funkwhale_api/history/admin.py            |  10 +-
 api/funkwhale_api/history/factories.py        |   2 +-
 .../history/migrations/0001_initial.py        |  52 +-
 .../migrations/0002_auto_20180325_1433.py     |  11 +-
 api/funkwhale_api/history/models.py           |  16 +-
 api/funkwhale_api/history/serializers.py      |  22 +-
 api/funkwhale_api/history/urls.py             |   3 +-
 api/funkwhale_api/history/views.py            |   7 +-
 api/funkwhale_api/instance/consumers.py       |   2 +-
 .../instance/dynamic_preferences_registry.py  |  94 +--
 api/funkwhale_api/instance/nodeinfo.py        |  89 +--
 api/funkwhale_api/instance/stats.py           |  18 +-
 api/funkwhale_api/instance/urls.py            |   7 +-
 api/funkwhale_api/instance/views.py           |  23 +-
 api/funkwhale_api/manage/filters.py           |  22 +-
 api/funkwhale_api/manage/serializers.py       |  59 +-
 api/funkwhale_api/manage/urls.py              |   6 +-
 api/funkwhale_api/manage/views.py             |  37 +-
 api/funkwhale_api/music/admin.py              |  82 +-
 api/funkwhale_api/music/factories.py          | 109 ++-
 api/funkwhale_api/music/fake_data.py          |  10 +-
 api/funkwhale_api/music/filters.py            |  72 +-
 api/funkwhale_api/music/importers.py          |  33 +-
 api/funkwhale_api/music/lyrics.py             |  10 +-
 .../management/commands/fix_track_files.py    |  80 +-
 api/funkwhale_api/music/metadata.py           | 256 +++---
 .../music/migrations/0001_initial.py          | 201 +++--
 .../migrations/0002_auto_20151215_1645.py     |  30 +-
 .../migrations/0003_auto_20151222_2233.py     |  10 +-
 .../music/migrations/0004_track_tags.py       |  17 +-
 .../music/migrations/0005_deduplicate.py      |  22 +-
 .../music/migrations/0006_unique_mbid.py      |  29 +-
 .../music/migrations/0007_track_position.py   |  10 +-
 .../migrations/0008_auto_20160529_1456.py     |  16 +-
 .../migrations/0009_auto_20160920_1614.py     |  77 +-
 .../migrations/0010_auto_20160920_1742.py     |  10 +-
 .../music/migrations/0011_rename_files.py     |  10 +-
 .../migrations/0012_auto_20161122_1905.py     |  14 +-
 .../migrations/0013_auto_20171213_2211.py     |  16 +-
 .../migrations/0014_importjob_track_file.py   |  18 +-
 .../0015_bind_track_file_to_import_job.py     |  14 +-
 .../0016_trackfile_acoustid_track_id.py       |  10 +-
 .../migrations/0017_auto_20171227_1728.py     |  26 +-
 .../migrations/0018_auto_20180218_1554.py     |  27 +-
 .../migrations/0019_populate_mimetypes.py     |  18 +-
 .../migrations/0020_importbatch_status.py     |  21 +-
 .../migrations/0021_populate_batch_status.py  |  13 +-
 .../0022_importbatch_import_request.py        |  19 +-
 .../migrations/0023_auto_20180407_1010.py     |  94 ++-
 .../music/migrations/0024_populate_uuid.py    |  60 +-
 .../migrations/0025_auto_20180419_2023.py     |  10 +-
 .../0026_trackfile_accessed_date.py           |  10 +-
 .../migrations/0027_auto_20180515_1808.py     |  24 +-
 api/funkwhale_api/music/models.py             | 449 +++++------
 api/funkwhale_api/music/permissions.py        |  13 +-
 api/funkwhale_api/music/serializers.py        | 203 ++---
 api/funkwhale_api/music/tasks.py              | 223 +++---
 api/funkwhale_api/music/utils.py              |  48 +-
 api/funkwhale_api/music/views.py              | 275 +++----
 api/funkwhale_api/musicbrainz/client.py       |  62 +-
 api/funkwhale_api/musicbrainz/urls.py         |  26 +-
 api/funkwhale_api/musicbrainz/views.py        |  27 +-
 api/funkwhale_api/playlists/admin.py          |   8 +-
 .../playlists/dynamic_preferences_registry.py |  12 +-
 api/funkwhale_api/playlists/factories.py      |   6 +-
 api/funkwhale_api/playlists/filters.py        |  11 +-
 .../playlists/migrations/0001_initial.py      |  88 +-
 .../migrations/0002_auto_20180316_2217.py     |  24 +-
 .../migrations/0003_auto_20180319_1214.py     |  43 +-
 .../migrations/0004_auto_20180320_1713.py     |  17 +-
 api/funkwhale_api/playlists/models.py         |  71 +-
 api/funkwhale_api/playlists/serializers.py    |  49 +-
 api/funkwhale_api/playlists/views.py          |  84 +-
 .../providers/acoustid/__init__.py            |   6 +-
 .../acoustid/dynamic_preferences_registry.py  |  14 +-
 .../management/commands/import_files.py       | 201 ++---
 .../providers/audiofile/tasks.py              |  48 +-
 api/funkwhale_api/providers/urls.py           |  18 +-
 api/funkwhale_api/providers/youtube/client.py |  41 +-
 .../youtube/dynamic_preferences_registry.py   |  14 +-
 api/funkwhale_api/providers/youtube/urls.py   |   4 +-
 api/funkwhale_api/providers/youtube/views.py  |  20 +-
 api/funkwhale_api/radios/admin.py             |  46 +-
 api/funkwhale_api/radios/factories.py         |  17 +-
 api/funkwhale_api/radios/filters.py           | 134 ++--
 api/funkwhale_api/radios/filtersets.py        |   5 +-
 .../radios/migrations/0001_initial.py         |  71 +-
 .../0002_radiosession_session_key.py          |  10 +-
 .../migrations/0003_auto_20160521_1708.py     |  19 +-
 .../migrations/0004_auto_20180107_1813.py     |  52 +-
 api/funkwhale_api/radios/models.py            |  48 +-
 api/funkwhale_api/radios/radios.py            |  67 +-
 api/funkwhale_api/radios/registries.py        |   6 +-
 api/funkwhale_api/radios/serializers.py       |  53 +-
 api/funkwhale_api/radios/urls.py              |   7 +-
 api/funkwhale_api/radios/views.py             |  65 +-
 api/funkwhale_api/requests/admin.py           |  12 +-
 api/funkwhale_api/requests/api_urls.py        |   6 +-
 api/funkwhale_api/requests/factories.py       |   8 +-
 api/funkwhale_api/requests/filters.py         |  15 +-
 .../requests/migrations/0001_initial.py       |  54 +-
 api/funkwhale_api/requests/models.py          |  22 +-
 api/funkwhale_api/requests/serializers.py     |  25 +-
 api/funkwhale_api/requests/views.py           |  18 +-
 api/funkwhale_api/subsonic/authentication.py  |  40 +-
 .../subsonic/dynamic_preferences_registry.py  |  16 +-
 api/funkwhale_api/subsonic/filters.py         |  12 +-
 api/funkwhale_api/subsonic/negotiation.py     |   6 +-
 api/funkwhale_api/subsonic/renderers.py       |  31 +-
 api/funkwhale_api/subsonic/serializers.py     | 212 +++--
 api/funkwhale_api/subsonic/views.py           | 479 +++++------
 api/funkwhale_api/taskapp/celery.py           |  16 +-
 api/funkwhale_api/users/adapters.py           |   5 +-
 api/funkwhale_api/users/admin.py              |  69 +-
 api/funkwhale_api/users/api_urls.py           |   2 +-
 .../users/dynamic_preferences_registry.py     |  28 +-
 api/funkwhale_api/users/factories.py          |  24 +-
 .../users/migrations/0001_initial.py          | 143 +++-
 .../migrations/0002_auto_20171214_2205.py     |  23 +-
 .../migrations/0003_auto_20171226_1357.py     |  16 +-
 .../migrations/0004_user_privacy_level.py     |  21 +-
 .../0005_user_subsonic_api_token.py           |  10 +-
 .../migrations/0006_auto_20180517_2324.py     |  16 +-
 .../migrations/0007_auto_20180524_2009.py     |  38 +-
 api/funkwhale_api/users/models.py             |  75 +-
 api/funkwhale_api/users/permissions.py        |   8 +-
 api/funkwhale_api/users/rest_auth_urls.py     |  23 +-
 api/funkwhale_api/users/serializers.py        |  44 +-
 api/funkwhale_api/users/views.py              |  42 +-
 api/tests/activity/test_record.py             |  15 +-
 api/tests/activity/test_serializers.py        |   7 +-
 api/tests/activity/test_utils.py              |  14 +-
 api/tests/activity/test_views.py              |  11 +-
 api/tests/channels/test_auth.py               |  20 +-
 api/tests/channels/test_consumers.py          |  12 +-
 api/tests/common/test_fields.py               |  17 +-
 api/tests/common/test_permissions.py          |  26 +-
 api/tests/common/test_preferences.py          |  43 +-
 api/tests/common/test_scripts.py              |  32 +-
 api/tests/common/test_serializers.py          |  91 +--
 api/tests/common/test_session.py              |  11 +-
 api/tests/conftest.py                         |  52 +-
 api/tests/data/youtube.py                     |  94 +--
 api/tests/favorites/test_activity.py          |  48 +-
 api/tests/favorites/test_favorites.py         |  93 ++-
 api/tests/federation/test_activity.py         |  37 +-
 api/tests/federation/test_actors.py           | 552 ++++++-------
 api/tests/federation/test_authentication.py   |  43 +-
 api/tests/federation/test_keys.py             |  30 +-
 api/tests/federation/test_library.py          |  73 +-
 api/tests/federation/test_models.py           |  23 +-
 api/tests/federation/test_permissions.py      |  52 +-
 api/tests/federation/test_serializers.py      | 751 +++++++++---------
 api/tests/federation/test_signing.py          |  99 +--
 api/tests/federation/test_tasks.py            | 138 ++--
 api/tests/federation/test_utils.py            |  64 +-
 api/tests/federation/test_views.py            | 416 +++++-----
 api/tests/federation/test_webfinger.py        |  74 +-
 api/tests/history/test_activity.py            |  48 +-
 api/tests/history/test_history.py             |  30 +-
 api/tests/instance/test_nodeinfo.py           | 142 ++--
 api/tests/instance/test_preferences.py        |  20 +-
 api/tests/instance/test_stats.py              |  45 +-
 api/tests/instance/test_views.py              |  33 +-
 api/tests/manage/test_serializers.py          |   2 +-
 api/tests/manage/test_views.py                |  21 +-
 api/tests/music/conftest.py                   | 641 ++++++++-------
 api/tests/music/test_activity.py              |  11 +-
 api/tests/music/test_api.py                   | 294 +++----
 api/tests/music/test_commands.py              |  41 +-
 api/tests/music/test_import.py                | 229 +++---
 api/tests/music/test_lyrics.py                |  53 +-
 api/tests/music/test_metadata.py              | 134 ++--
 api/tests/music/test_models.py                | 127 ++-
 api/tests/music/test_music.py                 | 153 ++--
 api/tests/music/test_permissions.py           |  54 +-
 api/tests/music/test_serializers.py           | 119 ++-
 api/tests/music/test_tasks.py                 | 256 +++---
 api/tests/music/test_utils.py                 |  36 +-
 api/tests/music/test_views.py                 | 372 ++++-----
 api/tests/music/test_works.py                 |  51 +-
 api/tests/musicbrainz/conftest.py             | 219 +++--
 api/tests/musicbrainz/test_api.py             | 100 +--
 api/tests/musicbrainz/test_cache.py           |  12 +-
 api/tests/playlists/test_models.py            |  61 +-
 api/tests/playlists/test_serializers.py       |  70 +-
 api/tests/playlists/test_views.py             | 177 ++---
 api/tests/radios/test_api.py                  | 130 ++-
 api/tests/radios/test_filters.py              | 153 ++--
 api/tests/radios/test_radios.py               | 138 ++--
 api/tests/requests/test_models.py             |  14 +-
 api/tests/requests/test_views.py              |  14 +-
 api/tests/subsonic/test_authentication.py     |  64 +-
 api/tests/subsonic/test_renderers.py          |  22 +-
 api/tests/subsonic/test_serializers.py        | 268 +++----
 api/tests/subsonic/test_views.py              | 414 +++++-----
 api/tests/test_acoustid.py                    |  45 +-
 api/tests/test_downloader.py                  |   8 +-
 api/tests/test_import_audio_file.py           | 185 ++---
 api/tests/test_jwt_querystring.py             |  13 +-
 api/tests/test_tasks.py                       |  11 +-
 api/tests/test_youtube.py                     |  81 +-
 api/tests/users/test_activity.py              |   9 +-
 api/tests/users/test_admin.py                 |  22 +-
 api/tests/users/test_jwt.py                   |   3 +-
 api/tests/users/test_models.py                |  78 +-
 api/tests/users/test_permissions.py           |  92 ++-
 api/tests/users/test_views.py                 | 185 ++---
 279 files changed, 8890 insertions(+), 9556 deletions(-)

diff --git a/api/config/api_urls.py b/api/config/api_urls.py
index 98b863a93c..f3f61313e5 100644
--- a/api/config/api_urls.py
+++ b/api/config/api_urls.py
@@ -12,70 +12,70 @@ from dynamic_preferences.api.viewsets import GlobalPreferencesViewSet
 from dynamic_preferences.users.viewsets import UserPreferencesViewSet
 
 router = routers.SimpleRouter()
-router.register(r'settings', GlobalPreferencesViewSet, base_name='settings')
-router.register(r'activity', activity_views.ActivityViewSet, 'activity')
-router.register(r'tags', views.TagViewSet, 'tags')
-router.register(r'tracks', views.TrackViewSet, 'tracks')
-router.register(r'trackfiles', views.TrackFileViewSet, 'trackfiles')
-router.register(r'artists', views.ArtistViewSet, 'artists')
-router.register(r'albums', views.AlbumViewSet, 'albums')
-router.register(r'import-batches', views.ImportBatchViewSet, 'import-batches')
-router.register(r'import-jobs', views.ImportJobViewSet, 'import-jobs')
-router.register(r'submit', views.SubmitViewSet, 'submit')
-router.register(r'playlists', playlists_views.PlaylistViewSet, 'playlists')
+router.register(r"settings", GlobalPreferencesViewSet, base_name="settings")
+router.register(r"activity", activity_views.ActivityViewSet, "activity")
+router.register(r"tags", views.TagViewSet, "tags")
+router.register(r"tracks", views.TrackViewSet, "tracks")
+router.register(r"trackfiles", views.TrackFileViewSet, "trackfiles")
+router.register(r"artists", views.ArtistViewSet, "artists")
+router.register(r"albums", views.AlbumViewSet, "albums")
+router.register(r"import-batches", views.ImportBatchViewSet, "import-batches")
+router.register(r"import-jobs", views.ImportJobViewSet, "import-jobs")
+router.register(r"submit", views.SubmitViewSet, "submit")
+router.register(r"playlists", playlists_views.PlaylistViewSet, "playlists")
 router.register(
-    r'playlist-tracks',
-    playlists_views.PlaylistTrackViewSet,
-    'playlist-tracks')
+    r"playlist-tracks", playlists_views.PlaylistTrackViewSet, "playlist-tracks"
+)
 v1_patterns = router.urls
 
 subsonic_router = routers.SimpleRouter(trailing_slash=False)
-subsonic_router.register(r'subsonic/rest', SubsonicViewSet, base_name='subsonic')
+subsonic_router.register(r"subsonic/rest", SubsonicViewSet, base_name="subsonic")
 
 
 v1_patterns += [
-    url(r'^instance/',
+    url(
+        r"^instance/",
+        include(("funkwhale_api.instance.urls", "instance"), namespace="instance"),
+    ),
+    url(
+        r"^manage/",
+        include(("funkwhale_api.manage.urls", "manage"), namespace="manage"),
+    ),
+    url(
+        r"^federation/",
         include(
-            ('funkwhale_api.instance.urls', 'instance'),
-            namespace='instance')),
-    url(r'^manage/',
-        include(
-            ('funkwhale_api.manage.urls', 'manage'),
-            namespace='manage')),
-    url(r'^federation/',
-        include(
-            ('funkwhale_api.federation.api_urls', 'federation'),
-            namespace='federation')),
-    url(r'^providers/',
-        include(
-            ('funkwhale_api.providers.urls', 'providers'),
-            namespace='providers')),
-    url(r'^favorites/',
-        include(
-            ('funkwhale_api.favorites.urls', 'favorites'),
-            namespace='favorites')),
-    url(r'^search$',
-        views.Search.as_view(), name='search'),
-    url(r'^radios/',
-        include(
-            ('funkwhale_api.radios.urls', 'radios'),
-            namespace='radios')),
-    url(r'^history/',
-        include(
-            ('funkwhale_api.history.urls', 'history'),
-            namespace='history')),
-    url(r'^users/',
-        include(
-            ('funkwhale_api.users.api_urls', 'users'),
-            namespace='users')),
-    url(r'^requests/',
-        include(
-            ('funkwhale_api.requests.api_urls', 'requests'),
-            namespace='requests')),
-    url(r'^token/$', jwt_views.obtain_jwt_token, name='token'),
-    url(r'^token/refresh/$', jwt_views.refresh_jwt_token, name='token_refresh'),
+            ("funkwhale_api.federation.api_urls", "federation"), namespace="federation"
+        ),
+    ),
+    url(
+        r"^providers/",
+        include(("funkwhale_api.providers.urls", "providers"), namespace="providers"),
+    ),
+    url(
+        r"^favorites/",
+        include(("funkwhale_api.favorites.urls", "favorites"), namespace="favorites"),
+    ),
+    url(r"^search$", views.Search.as_view(), name="search"),
+    url(
+        r"^radios/",
+        include(("funkwhale_api.radios.urls", "radios"), namespace="radios"),
+    ),
+    url(
+        r"^history/",
+        include(("funkwhale_api.history.urls", "history"), namespace="history"),
+    ),
+    url(
+        r"^users/",
+        include(("funkwhale_api.users.api_urls", "users"), namespace="users"),
+    ),
+    url(
+        r"^requests/",
+        include(("funkwhale_api.requests.api_urls", "requests"), namespace="requests"),
+    ),
+    url(r"^token/$", jwt_views.obtain_jwt_token, name="token"),
+    url(r"^token/refresh/$", jwt_views.refresh_jwt_token, name="token_refresh"),
 ]
 
 urlpatterns = [
-    url(r'^v1/', include((v1_patterns, 'v1'), namespace='v1'))
-] + format_suffix_patterns(subsonic_router.urls, allowed=['view'])
+    url(r"^v1/", include((v1_patterns, "v1"), namespace="v1"))
+] + format_suffix_patterns(subsonic_router.urls, allowed=["view"])
diff --git a/api/config/routing.py b/api/config/routing.py
index 574d5a18e2..b1f1637593 100644
--- a/api/config/routing.py
+++ b/api/config/routing.py
@@ -7,12 +7,13 @@ from funkwhale_api.common.auth import TokenAuthMiddleware
 from funkwhale_api.instance import consumers
 
 
-application = ProtocolTypeRouter({
-    # Empty for now (http->django views is added by default)
-    "websocket": TokenAuthMiddleware(
-        URLRouter([
-            url("^api/v1/instance/activity$",
-                consumers.InstanceActivityConsumer),
-        ])
-    ),
-})
+application = ProtocolTypeRouter(
+    {
+        # Empty for now (http->django views is added by default)
+        "websocket": TokenAuthMiddleware(
+            URLRouter(
+                [url("^api/v1/instance/activity$", consumers.InstanceActivityConsumer)]
+            )
+        )
+    }
+)
diff --git a/api/config/settings/common.py b/api/config/settings/common.py
index 6ab2a8303c..f7688a4e40 100644
--- a/api/config/settings/common.py
+++ b/api/config/settings/common.py
@@ -18,123 +18,117 @@ from celery.schedules import crontab
 from funkwhale_api import __version__
 
 ROOT_DIR = environ.Path(__file__) - 3  # (/a/b/myfile.py - 3 = /)
-APPS_DIR = ROOT_DIR.path('funkwhale_api')
+APPS_DIR = ROOT_DIR.path("funkwhale_api")
 
 env = environ.Env()
 
 try:
-    env.read_env(ROOT_DIR.file('.env'))
+    env.read_env(ROOT_DIR.file(".env"))
 except FileNotFoundError:
     pass
 
 FUNKWHALE_HOSTNAME = None
-FUNKWHALE_HOSTNAME_SUFFIX = env('FUNKWHALE_HOSTNAME_SUFFIX', default=None)
-FUNKWHALE_HOSTNAME_PREFIX = env('FUNKWHALE_HOSTNAME_PREFIX', default=None)
+FUNKWHALE_HOSTNAME_SUFFIX = env("FUNKWHALE_HOSTNAME_SUFFIX", default=None)
+FUNKWHALE_HOSTNAME_PREFIX = env("FUNKWHALE_HOSTNAME_PREFIX", default=None)
 if FUNKWHALE_HOSTNAME_PREFIX and FUNKWHALE_HOSTNAME_SUFFIX:
     # We're in traefik case, in development
-    FUNKWHALE_HOSTNAME = '{}.{}'.format(
-        FUNKWHALE_HOSTNAME_PREFIX, FUNKWHALE_HOSTNAME_SUFFIX)
-    FUNKWHALE_PROTOCOL = env('FUNKWHALE_PROTOCOL', default='https')
+    FUNKWHALE_HOSTNAME = "{}.{}".format(
+        FUNKWHALE_HOSTNAME_PREFIX, FUNKWHALE_HOSTNAME_SUFFIX
+    )
+    FUNKWHALE_PROTOCOL = env("FUNKWHALE_PROTOCOL", default="https")
 else:
     try:
-        FUNKWHALE_HOSTNAME = env('FUNKWHALE_HOSTNAME')
-        FUNKWHALE_PROTOCOL = env('FUNKWHALE_PROTOCOL', default='https')
+        FUNKWHALE_HOSTNAME = env("FUNKWHALE_HOSTNAME")
+        FUNKWHALE_PROTOCOL = env("FUNKWHALE_PROTOCOL", default="https")
     except Exception:
-        FUNKWHALE_URL = env('FUNKWHALE_URL')
+        FUNKWHALE_URL = env("FUNKWHALE_URL")
         _parsed = urlsplit(FUNKWHALE_URL)
         FUNKWHALE_HOSTNAME = _parsed.netloc
         FUNKWHALE_PROTOCOL = _parsed.scheme
 
-FUNKWHALE_URL = '{}://{}'.format(FUNKWHALE_PROTOCOL, FUNKWHALE_HOSTNAME)
+FUNKWHALE_URL = "{}://{}".format(FUNKWHALE_PROTOCOL, FUNKWHALE_HOSTNAME)
 
 
 # XXX: deprecated, see #186
-FEDERATION_ENABLED = env.bool('FEDERATION_ENABLED', default=True)
-FEDERATION_HOSTNAME = env('FEDERATION_HOSTNAME', default=FUNKWHALE_HOSTNAME)
+FEDERATION_ENABLED = env.bool("FEDERATION_ENABLED", default=True)
+FEDERATION_HOSTNAME = env("FEDERATION_HOSTNAME", default=FUNKWHALE_HOSTNAME)
 # XXX: deprecated, see #186
-FEDERATION_COLLECTION_PAGE_SIZE = env.int(
-    'FEDERATION_COLLECTION_PAGE_SIZE', default=50
-)
+FEDERATION_COLLECTION_PAGE_SIZE = env.int("FEDERATION_COLLECTION_PAGE_SIZE", default=50)
 # XXX: deprecated, see #186
 FEDERATION_MUSIC_NEEDS_APPROVAL = env.bool(
-    'FEDERATION_MUSIC_NEEDS_APPROVAL', default=True
+    "FEDERATION_MUSIC_NEEDS_APPROVAL", default=True
 )
 # XXX: deprecated, see #186
-FEDERATION_ACTOR_FETCH_DELAY = env.int(
-    'FEDERATION_ACTOR_FETCH_DELAY', default=60 * 12)
-ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS')
+FEDERATION_ACTOR_FETCH_DELAY = env.int("FEDERATION_ACTOR_FETCH_DELAY", default=60 * 12)
+ALLOWED_HOSTS = env.list("DJANGO_ALLOWED_HOSTS")
 
 # APP CONFIGURATION
 # ------------------------------------------------------------------------------
 DJANGO_APPS = (
-    'channels',
+    "channels",
     # Default Django apps:
-    'django.contrib.auth',
-    'django.contrib.contenttypes',
-    'django.contrib.sessions',
-    'django.contrib.sites',
-    'django.contrib.messages',
-    'django.contrib.staticfiles',
-    'django.contrib.postgres',
-
+    "django.contrib.auth",
+    "django.contrib.contenttypes",
+    "django.contrib.sessions",
+    "django.contrib.sites",
+    "django.contrib.messages",
+    "django.contrib.staticfiles",
+    "django.contrib.postgres",
     # Useful template tags:
     # 'django.contrib.humanize',
-
     # Admin
-    'django.contrib.admin',
+    "django.contrib.admin",
 )
 THIRD_PARTY_APPS = (
     # 'crispy_forms',  # Form layouts
-    'allauth',  # registration
-    'allauth.account',  # registration
-    'allauth.socialaccount',  # registration
-    'corsheaders',
-    'rest_framework',
-    'rest_framework.authtoken',
-    'taggit',
-    'rest_auth',
-    'rest_auth.registration',
-    'dynamic_preferences',
-    'django_filters',
-    'cacheops',
-    'django_cleanup',
+    "allauth",  # registration
+    "allauth.account",  # registration
+    "allauth.socialaccount",  # registration
+    "corsheaders",
+    "rest_framework",
+    "rest_framework.authtoken",
+    "taggit",
+    "rest_auth",
+    "rest_auth.registration",
+    "dynamic_preferences",
+    "django_filters",
+    "cacheops",
+    "django_cleanup",
 )
 
 
 # Sentry
 RAVEN_ENABLED = env.bool("RAVEN_ENABLED", default=False)
-RAVEN_DSN = env("RAVEN_DSN", default='')
+RAVEN_DSN = env("RAVEN_DSN", default="")
 
 if RAVEN_ENABLED:
     RAVEN_CONFIG = {
-        'dsn': RAVEN_DSN,
+        "dsn": RAVEN_DSN,
         # If you are using git, you can also automatically configure the
         # release based on the git info.
-        'release': __version__,
+        "release": __version__,
     }
-    THIRD_PARTY_APPS += (
-        'raven.contrib.django.raven_compat',
-    )
+    THIRD_PARTY_APPS += ("raven.contrib.django.raven_compat",)
 
 
 # Apps specific for this project go here.
 LOCAL_APPS = (
-    'funkwhale_api.common',
-    'funkwhale_api.activity.apps.ActivityConfig',
-    'funkwhale_api.users',  # custom users app
+    "funkwhale_api.common",
+    "funkwhale_api.activity.apps.ActivityConfig",
+    "funkwhale_api.users",  # custom users app
     # Your stuff: custom apps go here
-    'funkwhale_api.instance',
-    'funkwhale_api.music',
-    'funkwhale_api.requests',
-    'funkwhale_api.favorites',
-    'funkwhale_api.federation',
-    'funkwhale_api.radios',
-    'funkwhale_api.history',
-    'funkwhale_api.playlists',
-    'funkwhale_api.providers.audiofile',
-    'funkwhale_api.providers.youtube',
-    'funkwhale_api.providers.acoustid',
-    'funkwhale_api.subsonic',
+    "funkwhale_api.instance",
+    "funkwhale_api.music",
+    "funkwhale_api.requests",
+    "funkwhale_api.favorites",
+    "funkwhale_api.federation",
+    "funkwhale_api.radios",
+    "funkwhale_api.history",
+    "funkwhale_api.playlists",
+    "funkwhale_api.providers.audiofile",
+    "funkwhale_api.providers.youtube",
+    "funkwhale_api.providers.acoustid",
+    "funkwhale_api.subsonic",
 )
 
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
@@ -145,20 +139,18 @@ INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
 # ------------------------------------------------------------------------------
 MIDDLEWARE = (
     # Make sure djangosecure.middleware.SecurityMiddleware is listed first
-    'django.contrib.sessions.middleware.SessionMiddleware',
-    'corsheaders.middleware.CorsMiddleware',
-    'django.middleware.common.CommonMiddleware',
-    'django.middleware.csrf.CsrfViewMiddleware',
-    'django.contrib.auth.middleware.AuthenticationMiddleware',
-    'django.contrib.messages.middleware.MessageMiddleware',
-    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+    "django.contrib.sessions.middleware.SessionMiddleware",
+    "corsheaders.middleware.CorsMiddleware",
+    "django.middleware.common.CommonMiddleware",
+    "django.middleware.csrf.CsrfViewMiddleware",
+    "django.contrib.auth.middleware.AuthenticationMiddleware",
+    "django.contrib.messages.middleware.MessageMiddleware",
+    "django.middleware.clickjacking.XFrameOptionsMiddleware",
 )
 
 # MIGRATIONS CONFIGURATION
 # ------------------------------------------------------------------------------
-MIGRATION_MODULES = {
-    'sites': 'funkwhale_api.contrib.sites.migrations'
-}
+MIGRATION_MODULES = {"sites": "funkwhale_api.contrib.sites.migrations"}
 
 # DEBUG
 # ------------------------------------------------------------------------------
@@ -168,9 +160,7 @@ DEBUG = env.bool("DJANGO_DEBUG", False)
 # FIXTURE CONFIGURATION
 # ------------------------------------------------------------------------------
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FIXTURE_DIRS
-FIXTURE_DIRS = (
-    str(APPS_DIR.path('fixtures')),
-)
+FIXTURE_DIRS = (str(APPS_DIR.path("fixtures")),)
 
 # EMAIL CONFIGURATION
 # ------------------------------------------------------------------------------
@@ -178,16 +168,14 @@ FIXTURE_DIRS = (
 # EMAIL
 # ------------------------------------------------------------------------------
 DEFAULT_FROM_EMAIL = env(
-    'DEFAULT_FROM_EMAIL',
-    default='Funkwhale <noreply@{}>'.format(FUNKWHALE_HOSTNAME))
+    "DEFAULT_FROM_EMAIL", default="Funkwhale <noreply@{}>".format(FUNKWHALE_HOSTNAME)
+)
 
-EMAIL_SUBJECT_PREFIX = env(
-    "EMAIL_SUBJECT_PREFIX", default='[Funkwhale] ')
-SERVER_EMAIL = env('SERVER_EMAIL', default=DEFAULT_FROM_EMAIL)
+EMAIL_SUBJECT_PREFIX = env("EMAIL_SUBJECT_PREFIX", default="[Funkwhale] ")
+SERVER_EMAIL = env("SERVER_EMAIL", default=DEFAULT_FROM_EMAIL)
 
 
-EMAIL_CONFIG = env.email_url(
-    'EMAIL_CONFIG', default='consolemail://')
+EMAIL_CONFIG = env.email_url("EMAIL_CONFIG", default="consolemail://")
 
 vars().update(EMAIL_CONFIG)
 
@@ -196,9 +184,9 @@ vars().update(EMAIL_CONFIG)
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#databases
 DATABASES = {
     # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
-    'default': env.db("DATABASE_URL"),
+    "default": env.db("DATABASE_URL")
 }
-DATABASES['default']['ATOMIC_REQUESTS'] = True
+DATABASES["default"]["ATOMIC_REQUESTS"] = True
 #
 # DATABASES = {
 #     'default': {
@@ -212,10 +200,10 @@ DATABASES['default']['ATOMIC_REQUESTS'] = True
 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
 # although not all choices may be available on all operating systems.
 # In a Windows environment this must be set to your system time zone.
-TIME_ZONE = 'UTC'
+TIME_ZONE = "UTC"
 
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#language-code
-LANGUAGE_CODE = 'en-us'
+LANGUAGE_CODE = "en-us"
 
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#site-id
 SITE_ID = 1
@@ -235,126 +223,120 @@ USE_TZ = True
 TEMPLATES = [
     {
         # See: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-TEMPLATES-BACKEND
-        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        "BACKEND": "django.template.backends.django.DjangoTemplates",
         # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-dirs
-        'DIRS': [
-            str(APPS_DIR.path('templates')),
-        ],
-        'OPTIONS': {
+        "DIRS": [str(APPS_DIR.path("templates"))],
+        "OPTIONS": {
             # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-debug
-            'debug': DEBUG,
+            "debug": DEBUG,
             # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-loaders
             # https://docs.djangoproject.com/en/dev/ref/templates/api/#loader-types
-            'loaders': [
-                'django.template.loaders.filesystem.Loader',
-                'django.template.loaders.app_directories.Loader',
+            "loaders": [
+                "django.template.loaders.filesystem.Loader",
+                "django.template.loaders.app_directories.Loader",
             ],
             # See: https://docs.djangoproject.com/en/dev/ref/settings/#template-context-processors
-            'context_processors': [
-                'django.template.context_processors.debug',
-                'django.template.context_processors.request',
-                'django.contrib.auth.context_processors.auth',
-                'django.template.context_processors.i18n',
-                'django.template.context_processors.media',
-                'django.template.context_processors.static',
-                'django.template.context_processors.tz',
-                'django.contrib.messages.context_processors.messages',
+            "context_processors": [
+                "django.template.context_processors.debug",
+                "django.template.context_processors.request",
+                "django.contrib.auth.context_processors.auth",
+                "django.template.context_processors.i18n",
+                "django.template.context_processors.media",
+                "django.template.context_processors.static",
+                "django.template.context_processors.tz",
+                "django.contrib.messages.context_processors.messages",
                 # Your stuff: custom template context processors go here
             ],
         },
-    },
+    }
 ]
 
 # See: http://django-crispy-forms.readthedocs.org/en/latest/install.html#template-packs
-CRISPY_TEMPLATE_PACK = 'bootstrap3'
+CRISPY_TEMPLATE_PACK = "bootstrap3"
 
 # STATIC FILE CONFIGURATION
 # ------------------------------------------------------------------------------
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-root
-STATIC_ROOT = env("STATIC_ROOT", default=str(ROOT_DIR('staticfiles')))
+STATIC_ROOT = env("STATIC_ROOT", default=str(ROOT_DIR("staticfiles")))
 
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#static-url
-STATIC_URL = env("STATIC_URL", default='/staticfiles/')
-DEFAULT_FILE_STORAGE = 'funkwhale_api.common.storage.ASCIIFileSystemStorage'
+STATIC_URL = env("STATIC_URL", default="/staticfiles/")
+DEFAULT_FILE_STORAGE = "funkwhale_api.common.storage.ASCIIFileSystemStorage"
 
 # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#std:setting-STATICFILES_DIRS
-STATICFILES_DIRS = (
-    str(APPS_DIR.path('static')),
-)
+STATICFILES_DIRS = (str(APPS_DIR.path("static")),)
 
 # See: https://docs.djangoproject.com/en/dev/ref/contrib/staticfiles/#staticfiles-finders
 STATICFILES_FINDERS = (
-    'django.contrib.staticfiles.finders.FileSystemFinder',
-    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+    "django.contrib.staticfiles.finders.FileSystemFinder",
+    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
 )
 
 # MEDIA CONFIGURATION
 # ------------------------------------------------------------------------------
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-root
-MEDIA_ROOT = env("MEDIA_ROOT", default=str(APPS_DIR('media')))
+MEDIA_ROOT = env("MEDIA_ROOT", default=str(APPS_DIR("media")))
 
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#media-url
-MEDIA_URL = env("MEDIA_URL", default='/media/')
+MEDIA_URL = env("MEDIA_URL", default="/media/")
 
 # URL Configuration
 # ------------------------------------------------------------------------------
-ROOT_URLCONF = 'config.urls'
+ROOT_URLCONF = "config.urls"
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#wsgi-application
-WSGI_APPLICATION = 'config.wsgi.application'
+WSGI_APPLICATION = "config.wsgi.application"
 ASGI_APPLICATION = "config.routing.application"
 
 # This ensures that Django will be able to detect a secure connection
-SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
+SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
 
 # AUTHENTICATION CONFIGURATION
 # ------------------------------------------------------------------------------
 AUTHENTICATION_BACKENDS = (
-    'django.contrib.auth.backends.ModelBackend',
-    'allauth.account.auth_backends.AuthenticationBackend',
+    "django.contrib.auth.backends.ModelBackend",
+    "allauth.account.auth_backends.AuthenticationBackend",
 )
 SESSION_COOKIE_HTTPONLY = False
 # Some really nice defaults
-ACCOUNT_AUTHENTICATION_METHOD = 'username_email'
+ACCOUNT_AUTHENTICATION_METHOD = "username_email"
 ACCOUNT_EMAIL_REQUIRED = True
-ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
+ACCOUNT_EMAIL_VERIFICATION = "mandatory"
 
 # Custom user app defaults
 # Select the correct user model
-AUTH_USER_MODEL = 'users.User'
-LOGIN_REDIRECT_URL = 'users:redirect'
-LOGIN_URL = 'account_login'
+AUTH_USER_MODEL = "users.User"
+LOGIN_REDIRECT_URL = "users:redirect"
+LOGIN_URL = "account_login"
 
 # SLUGLIFIER
-AUTOSLUG_SLUGIFY_FUNCTION = 'slugify.slugify'
+AUTOSLUG_SLUGIFY_FUNCTION = "slugify.slugify"
 
 CACHE_DEFAULT = "redis://127.0.0.1:6379/0"
-CACHES = {
-    "default": env.cache_url('CACHE_URL', default=CACHE_DEFAULT)
-}
+CACHES = {"default": env.cache_url("CACHE_URL", default=CACHE_DEFAULT)}
 
 CACHES["default"]["BACKEND"] = "django_redis.cache.RedisCache"
 from urllib.parse import urlparse
-cache_url = urlparse(CACHES['default']['LOCATION'])
+
+cache_url = urlparse(CACHES["default"]["LOCATION"])
 CHANNEL_LAYERS = {
     "default": {
         "BACKEND": "channels_redis.core.RedisChannelLayer",
-        "CONFIG": {
-            "hosts": [(cache_url.hostname, cache_url.port)],
-        },
-    },
+        "CONFIG": {"hosts": [(cache_url.hostname, cache_url.port)]},
+    }
 }
 
 CACHES["default"]["OPTIONS"] = {
     "CLIENT_CLASS": "django_redis.client.DefaultClient",
     "IGNORE_EXCEPTIONS": True,  # mimics memcache behavior.
-                                # http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior
+    # http://niwinz.github.io/django-redis/latest/#_memcached_exceptions_behavior
 }
 
 
 ########## CELERY
-INSTALLED_APPS += ('funkwhale_api.taskapp.celery.CeleryConfig',)
+INSTALLED_APPS += ("funkwhale_api.taskapp.celery.CeleryConfig",)
 CELERY_BROKER_URL = env(
-    "CELERY_BROKER_URL", default=env('CACHE_URL', default=CACHE_DEFAULT))
+    "CELERY_BROKER_URL", default=env("CACHE_URL", default=CACHE_DEFAULT)
+)
 ########## END CELERY
 # Location of root django.contrib.admin URL, use {% url 'admin:index' %}
 
@@ -362,25 +344,24 @@ CELERY_BROKER_URL = env(
 CELERY_TASK_DEFAULT_RATE_LIMIT = 1
 CELERY_TASK_TIME_LIMIT = 300
 CELERYBEAT_SCHEDULE = {
-    'federation.clean_music_cache': {
-        'task': 'funkwhale_api.federation.tasks.clean_music_cache',
-        'schedule': crontab(hour='*/2'),
-        'options': {
-            'expires': 60 * 2,
-        },
+    "federation.clean_music_cache": {
+        "task": "funkwhale_api.federation.tasks.clean_music_cache",
+        "schedule": crontab(hour="*/2"),
+        "options": {"expires": 60 * 2},
     }
 }
 
 import datetime
+
 JWT_AUTH = {
-    'JWT_ALLOW_REFRESH': True,
-    'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
-    'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=30),
-    'JWT_AUTH_HEADER_PREFIX': 'JWT',
-    'JWT_GET_USER_SECRET_KEY': lambda user: user.secret_key
+    "JWT_ALLOW_REFRESH": True,
+    "JWT_EXPIRATION_DELTA": datetime.timedelta(days=7),
+    "JWT_REFRESH_EXPIRATION_DELTA": datetime.timedelta(days=30),
+    "JWT_AUTH_HEADER_PREFIX": "JWT",
+    "JWT_GET_USER_SECRET_KEY": lambda user: user.secret_key,
 }
 OLD_PASSWORD_FIELD_ENABLED = True
-ACCOUNT_ADAPTER = 'funkwhale_api.users.adapters.FunkwhaleAccountAdapter'
+ACCOUNT_ADAPTER = "funkwhale_api.users.adapters.FunkwhaleAccountAdapter"
 CORS_ORIGIN_ALLOW_ALL = True
 # CORS_ORIGIN_WHITELIST = (
 #     'localhost',
@@ -389,41 +370,37 @@ CORS_ORIGIN_ALLOW_ALL = True
 CORS_ALLOW_CREDENTIALS = True
 
 REST_FRAMEWORK = {
-    'DEFAULT_PERMISSION_CLASSES': (
-        'rest_framework.permissions.IsAuthenticated',
+    "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
+    "DEFAULT_PAGINATION_CLASS": "funkwhale_api.common.pagination.FunkwhalePagination",
+    "PAGE_SIZE": 25,
+    "DEFAULT_PARSER_CLASSES": (
+        "rest_framework.parsers.JSONParser",
+        "rest_framework.parsers.FormParser",
+        "rest_framework.parsers.MultiPartParser",
+        "funkwhale_api.federation.parsers.ActivityParser",
     ),
-    'DEFAULT_PAGINATION_CLASS': 'funkwhale_api.common.pagination.FunkwhalePagination',
-    'PAGE_SIZE': 25,
-    'DEFAULT_PARSER_CLASSES': (
-        'rest_framework.parsers.JSONParser',
-        'rest_framework.parsers.FormParser',
-        'rest_framework.parsers.MultiPartParser',
-        'funkwhale_api.federation.parsers.ActivityParser',
+    "DEFAULT_AUTHENTICATION_CLASSES": (
+        "funkwhale_api.common.authentication.JSONWebTokenAuthenticationQS",
+        "funkwhale_api.common.authentication.BearerTokenHeaderAuth",
+        "rest_framework_jwt.authentication.JSONWebTokenAuthentication",
+        "rest_framework.authentication.SessionAuthentication",
+        "rest_framework.authentication.BasicAuthentication",
     ),
-    'DEFAULT_AUTHENTICATION_CLASSES': (
-        'funkwhale_api.common.authentication.JSONWebTokenAuthenticationQS',
-        'funkwhale_api.common.authentication.BearerTokenHeaderAuth',
-        'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
-        'rest_framework.authentication.SessionAuthentication',
-        'rest_framework.authentication.BasicAuthentication',
+    "DEFAULT_FILTER_BACKENDS": (
+        "rest_framework.filters.OrderingFilter",
+        "django_filters.rest_framework.DjangoFilterBackend",
     ),
-    'DEFAULT_FILTER_BACKENDS': (
-        'rest_framework.filters.OrderingFilter',
-        'django_filters.rest_framework.DjangoFilterBackend',
-    ),
-    'DEFAULT_RENDERER_CLASSES': (
-        'rest_framework.renderers.JSONRenderer',
-    )
+    "DEFAULT_RENDERER_CLASSES": ("rest_framework.renderers.JSONRenderer",),
 }
 
-BROWSABLE_API_ENABLED = env.bool('BROWSABLE_API_ENABLED', default=False)
+BROWSABLE_API_ENABLED = env.bool("BROWSABLE_API_ENABLED", default=False)
 if BROWSABLE_API_ENABLED:
-    REST_FRAMEWORK['DEFAULT_RENDERER_CLASSES'] += (
-        'rest_framework.renderers.BrowsableAPIRenderer',
+    REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] += (
+        "rest_framework.renderers.BrowsableAPIRenderer",
     )
 
 REST_AUTH_SERIALIZERS = {
-    'PASSWORD_RESET_SERIALIZER': 'funkwhale_api.users.serializers.PasswordResetSerializer'  # noqa
+    "PASSWORD_RESET_SERIALIZER": "funkwhale_api.users.serializers.PasswordResetSerializer"  # noqa
 }
 REST_SESSION_LOGIN = False
 REST_USE_JWT = True
@@ -434,60 +411,55 @@ USE_X_FORWARDED_PORT = True
 
 # Wether we should use Apache, Nginx (or other) headers when serving audio files
 # Default to Nginx
-REVERSE_PROXY_TYPE = env('REVERSE_PROXY_TYPE', default='nginx')
-assert REVERSE_PROXY_TYPE in ['apache2', 'nginx'], 'Unsupported REVERSE_PROXY_TYPE'
+REVERSE_PROXY_TYPE = env("REVERSE_PROXY_TYPE", default="nginx")
+assert REVERSE_PROXY_TYPE in ["apache2", "nginx"], "Unsupported REVERSE_PROXY_TYPE"
 
 # Which path will be used to process the internal redirection
 # **DO NOT** put a slash at the end
-PROTECT_FILES_PATH = env('PROTECT_FILES_PATH', default='/_protected')
+PROTECT_FILES_PATH = env("PROTECT_FILES_PATH", default="/_protected")
 
 
 # use this setting to tweak for how long you want to cache
 # musicbrainz results. (value is in seconds)
-MUSICBRAINZ_CACHE_DURATION = env.int(
-    'MUSICBRAINZ_CACHE_DURATION',
-    default=300
-)
-CACHEOPS_REDIS = env('CACHE_URL', default=CACHE_DEFAULT)
-CACHEOPS_ENABLED = env.bool('CACHEOPS_ENABLED', default=True)
+MUSICBRAINZ_CACHE_DURATION = env.int("MUSICBRAINZ_CACHE_DURATION", default=300)
+CACHEOPS_REDIS = env("CACHE_URL", default=CACHE_DEFAULT)
+CACHEOPS_ENABLED = env.bool("CACHEOPS_ENABLED", default=True)
 CACHEOPS = {
-    'music.artist': {'ops': 'all', 'timeout': 60 * 60},
-    'music.album': {'ops': 'all', 'timeout': 60 * 60},
-    'music.track': {'ops': 'all', 'timeout': 60 * 60},
-    'music.trackfile': {'ops': 'all', 'timeout': 60 * 60},
-    'taggit.tag': {'ops': 'all', 'timeout': 60 * 60},
+    "music.artist": {"ops": "all", "timeout": 60 * 60},
+    "music.album": {"ops": "all", "timeout": 60 * 60},
+    "music.track": {"ops": "all", "timeout": 60 * 60},
+    "music.trackfile": {"ops": "all", "timeout": 60 * 60},
+    "taggit.tag": {"ops": "all", "timeout": 60 * 60},
 }
 
 # Custom Admin URL, use {% url 'admin:index' %}
-ADMIN_URL = env('DJANGO_ADMIN_URL', default='^api/admin/')
+ADMIN_URL = env("DJANGO_ADMIN_URL", default="^api/admin/")
 CSRF_USE_SESSIONS = True
 
 # Playlist settings
 # XXX: deprecated, see #186
-PLAYLISTS_MAX_TRACKS = env.int('PLAYLISTS_MAX_TRACKS', default=250)
+PLAYLISTS_MAX_TRACKS = env.int("PLAYLISTS_MAX_TRACKS", default=250)
 
 ACCOUNT_USERNAME_BLACKLIST = [
-    'funkwhale',
-    'library',
-    'test',
-    'status',
-    'root',
-    'admin',
-    'owner',
-    'superuser',
-    'staff',
-    'service',
-] + env.list('ACCOUNT_USERNAME_BLACKLIST', default=[])
-
-EXTERNAL_REQUESTS_VERIFY_SSL = env.bool(
-    'EXTERNAL_REQUESTS_VERIFY_SSL',
-    default=True
-)
+    "funkwhale",
+    "library",
+    "test",
+    "status",
+    "root",
+    "admin",
+    "owner",
+    "superuser",
+    "staff",
+    "service",
+] + env.list("ACCOUNT_USERNAME_BLACKLIST", default=[])
+
+EXTERNAL_REQUESTS_VERIFY_SSL = env.bool("EXTERNAL_REQUESTS_VERIFY_SSL", default=True)
 # XXX: deprecated, see #186
 API_AUTHENTICATION_REQUIRED = env.bool("API_AUTHENTICATION_REQUIRED", True)
 
-MUSIC_DIRECTORY_PATH = env('MUSIC_DIRECTORY_PATH', default=None)
+MUSIC_DIRECTORY_PATH = env("MUSIC_DIRECTORY_PATH", default=None)
 # on Docker setup, the music directory may not match the host path,
 # and we need to know it for it to serve stuff properly
 MUSIC_DIRECTORY_SERVE_PATH = env(
-    'MUSIC_DIRECTORY_SERVE_PATH', default=MUSIC_DIRECTORY_PATH)
+    "MUSIC_DIRECTORY_SERVE_PATH", default=MUSIC_DIRECTORY_PATH
+)
diff --git a/api/config/settings/local.py b/api/config/settings/local.py
index df14945cc9..51e7934765 100644
--- a/api/config/settings/local.py
+++ b/api/config/settings/local.py
@@ -1,53 +1,53 @@
 # -*- coding: utf-8 -*-
-'''
+"""
 Local settings
 
 - Run in Debug mode
 - Use console backend for emails
 - Add Django Debug Toolbar
 - Add django-extensions as app
-'''
+"""
 
 from .common import *  # noqa
 
 # DEBUG
 # ------------------------------------------------------------------------------
-DEBUG = env.bool('DJANGO_DEBUG', default=True)
-TEMPLATES[0]['OPTIONS']['debug'] = DEBUG
+DEBUG = env.bool("DJANGO_DEBUG", default=True)
+TEMPLATES[0]["OPTIONS"]["debug"] = DEBUG
 
 # SECRET CONFIGURATION
 # ------------------------------------------------------------------------------
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key
 # Note: This key only used for development and testing.
-SECRET_KEY = env("DJANGO_SECRET_KEY", default='mc$&b=5j#6^bv7tld1gyjp2&+^-qrdy=0sw@r5sua*1zp4fmxc')
+SECRET_KEY = env(
+    "DJANGO_SECRET_KEY", default="mc$&b=5j#6^bv7tld1gyjp2&+^-qrdy=0sw@r5sua*1zp4fmxc"
+)
 
 # Mail settings
 # ------------------------------------------------------------------------------
-EMAIL_HOST = 'localhost'
+EMAIL_HOST = "localhost"
 EMAIL_PORT = 1025
 
 # django-debug-toolbar
 # ------------------------------------------------------------------------------
-MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
+MIDDLEWARE += ("debug_toolbar.middleware.DebugToolbarMiddleware",)
 
 # INTERNAL_IPS = ('127.0.0.1', '10.0.2.2',)
 
 DEBUG_TOOLBAR_CONFIG = {
-    'DISABLE_PANELS': [
-        'debug_toolbar.panels.redirects.RedirectsPanel',
-    ],
-    'SHOW_TEMPLATE_CONTEXT': True,
-    'SHOW_TOOLBAR_CALLBACK': lambda request: True,
+    "DISABLE_PANELS": ["debug_toolbar.panels.redirects.RedirectsPanel"],
+    "SHOW_TEMPLATE_CONTEXT": True,
+    "SHOW_TOOLBAR_CALLBACK": lambda request: True,
 }
 
 # django-extensions
 # ------------------------------------------------------------------------------
 # INSTALLED_APPS += ('django_extensions', )
-INSTALLED_APPS += ('debug_toolbar', )
+INSTALLED_APPS += ("debug_toolbar",)
 
 # TESTING
 # ------------------------------------------------------------------------------
-TEST_RUNNER = 'django.test.runner.DiscoverRunner'
+TEST_RUNNER = "django.test.runner.DiscoverRunner"
 
 ########## CELERY
 # In development, all tasks will be executed locally by blocking until the task returns
@@ -57,23 +57,15 @@ CELERY_TASK_ALWAYS_EAGER = False
 # Your local stuff: Below this line define 3rd party library settings
 
 LOGGING = {
-    'version': 1,
-    'handlers': {
-        'console':{
-            'level':'DEBUG',
-            'class':'logging.StreamHandler',
-        },
-    },
-    'loggers': {
-        'django.request': {
-            'handlers':['console'],
-            'propagate': True,
-            'level':'DEBUG',
-        },
-        '': {
-            'level': 'DEBUG',
-            'handlers': ['console'],
+    "version": 1,
+    "handlers": {"console": {"level": "DEBUG", "class": "logging.StreamHandler"}},
+    "loggers": {
+        "django.request": {
+            "handlers": ["console"],
+            "propagate": True,
+            "level": "DEBUG",
         },
+        "": {"level": "DEBUG", "handlers": ["console"]},
     },
 }
 CSRF_TRUSTED_ORIGINS = [o for o in ALLOWED_HOSTS]
diff --git a/api/config/settings/production.py b/api/config/settings/production.py
index 39be40dc32..0c79ee2ef4 100644
--- a/api/config/settings/production.py
+++ b/api/config/settings/production.py
@@ -1,5 +1,5 @@
 # -*- coding: utf-8 -*-
-'''
+"""
 Production Configurations
 
 - Use djangosecure
@@ -8,7 +8,7 @@ Production Configurations
 - Use Redis on Heroku
 
 
-'''
+"""
 from __future__ import absolute_import, unicode_literals
 
 from django.utils import six
@@ -58,19 +58,24 @@ CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS
 # ------------------------------------------------------------------------------
 # Uploaded Media Files
 # ------------------------
-DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
+DEFAULT_FILE_STORAGE = "django.core.files.storage.FileSystemStorage"
 
 # Static Assets
 # ------------------------
-STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
+STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
 
 # TEMPLATE CONFIGURATION
 # ------------------------------------------------------------------------------
 # See:
 # https://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.loaders.cached.Loader
-TEMPLATES[0]['OPTIONS']['loaders'] = [
-    ('django.template.loaders.cached.Loader', [
-        'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader', ]),
+TEMPLATES[0]["OPTIONS"]["loaders"] = [
+    (
+        "django.template.loaders.cached.Loader",
+        [
+            "django.template.loaders.filesystem.Loader",
+            "django.template.loaders.app_directories.Loader",
+        ],
+    )
 ]
 
 # CACHING
@@ -78,7 +83,6 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [
 # Heroku URL does not pass the DB number, so we parse it in
 
 
-
 # LOGGING CONFIGURATION
 # ------------------------------------------------------------------------------
 # See: https://docs.djangoproject.com/en/dev/ref/settings/#logging
@@ -88,43 +92,39 @@ TEMPLATES[0]['OPTIONS']['loaders'] = [
 # See http://docs.djangoproject.com/en/dev/topics/logging for
 # more details on how to customize your logging configuration.
 LOGGING = {
-    'version': 1,
-    'disable_existing_loggers': False,
-    'filters': {
-        'require_debug_false': {
-            '()': 'django.utils.log.RequireDebugFalse'
+    "version": 1,
+    "disable_existing_loggers": False,
+    "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}},
+    "formatters": {
+        "verbose": {
+            "format": "%(levelname)s %(asctime)s %(module)s "
+            "%(process)d %(thread)d %(message)s"
         }
     },
-    'formatters': {
-        'verbose': {
-            'format': '%(levelname)s %(asctime)s %(module)s '
-                      '%(process)d %(thread)d %(message)s'
+    "handlers": {
+        "mail_admins": {
+            "level": "ERROR",
+            "filters": ["require_debug_false"],
+            "class": "django.utils.log.AdminEmailHandler",
+        },
+        "console": {
+            "level": "DEBUG",
+            "class": "logging.StreamHandler",
+            "formatter": "verbose",
         },
     },
-    'handlers': {
-        'mail_admins': {
-            'level': 'ERROR',
-            'filters': ['require_debug_false'],
-            'class': 'django.utils.log.AdminEmailHandler'
+    "loggers": {
+        "django.request": {
+            "handlers": ["mail_admins"],
+            "level": "ERROR",
+            "propagate": True,
         },
-        'console': {
-            'level': 'DEBUG',
-            'class': 'logging.StreamHandler',
-            'formatter': 'verbose',
+        "django.security.DisallowedHost": {
+            "level": "ERROR",
+            "handlers": ["console", "mail_admins"],
+            "propagate": True,
         },
     },
-    'loggers': {
-        'django.request': {
-            'handlers': ['mail_admins'],
-            'level': 'ERROR',
-            'propagate': True
-        },
-        'django.security.DisallowedHost': {
-            'level': 'ERROR',
-            'handlers': ['console', 'mail_admins'],
-            'propagate': True
-        }
-    }
 }
 
 
diff --git a/api/config/urls.py b/api/config/urls.py
index 90598ea841..60753294c6 100644
--- a/api/config/urls.py
+++ b/api/config/urls.py
@@ -11,32 +11,30 @@ from django.views import defaults as default_views
 urlpatterns = [
     # Django Admin, use {% url 'admin:index' %}
     url(settings.ADMIN_URL, admin.site.urls),
-
-    url(r'^api/', include(("config.api_urls", 'api'), namespace="api")),
-    url(r'^', include(
-        ('funkwhale_api.federation.urls', 'federation'),
-        namespace="federation")),
-    url(r'^api/v1/auth/', include('rest_auth.urls')),
-    url(r'^api/v1/auth/registration/', include('funkwhale_api.users.rest_auth_urls')),
-    url(r'^accounts/', include('allauth.urls')),
-
+    url(r"^api/", include(("config.api_urls", "api"), namespace="api")),
+    url(
+        r"^",
+        include(
+            ("funkwhale_api.federation.urls", "federation"), namespace="federation"
+        ),
+    ),
+    url(r"^api/v1/auth/", include("rest_auth.urls")),
+    url(r"^api/v1/auth/registration/", include("funkwhale_api.users.rest_auth_urls")),
+    url(r"^accounts/", include("allauth.urls")),
     # Your stuff: custom urls includes go here
-
-
 ]
 
 if settings.DEBUG:
     # This allows the error pages to be debugged during development, just visit
     # these url in browser to see how these error pages look like.
     urlpatterns += [
-        url(r'^400/$', default_views.bad_request),
-        url(r'^403/$', default_views.permission_denied),
-        url(r'^404/$', default_views.page_not_found),
-        url(r'^500/$', default_views.server_error),
+        url(r"^400/$", default_views.bad_request),
+        url(r"^403/$", default_views.permission_denied),
+        url(r"^404/$", default_views.page_not_found),
+        url(r"^500/$", default_views.server_error),
     ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
 
-    if 'debug_toolbar' in settings.INSTALLED_APPS:
+    if "debug_toolbar" in settings.INSTALLED_APPS:
         import debug_toolbar
-        urlpatterns += [
-            url(r'^__debug__/', include(debug_toolbar.urls)),
-        ]
+
+        urlpatterns += [url(r"^__debug__/", include(debug_toolbar.urls))]
diff --git a/api/demo/demo-user.py b/api/demo/demo-user.py
index 4f8648fb37..94757d2faa 100644
--- a/api/demo/demo-user.py
+++ b/api/demo/demo-user.py
@@ -1,7 +1,7 @@
 from funkwhale_api.users.models import User
 
 
-u = User.objects.create(email='demo@demo.com', username='demo', is_staff=True)
-u.set_password('demo')
-u.subsonic_api_token = 'demo'
+u = User.objects.create(email="demo@demo.com", username="demo", is_staff=True)
+u.set_password("demo")
+u.subsonic_api_token = "demo"
 u.save()
diff --git a/api/funkwhale_api/__init__.py b/api/funkwhale_api/__init__.py
index 8b5b81ad4b..0162c23173 100644
--- a/api/funkwhale_api/__init__.py
+++ b/api/funkwhale_api/__init__.py
@@ -1,3 +1,8 @@
 # -*- coding: utf-8 -*-
-__version__ = '0.14.1'
-__version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')])
+__version__ = "0.14.1"
+__version_info__ = tuple(
+    [
+        int(num) if num.isdigit() else num
+        for num in __version__.replace("-", ".", 1).split(".")
+    ]
+)
diff --git a/api/funkwhale_api/activity/apps.py b/api/funkwhale_api/activity/apps.py
index 0c66cbf50c..b70f65c571 100644
--- a/api/funkwhale_api/activity/apps.py
+++ b/api/funkwhale_api/activity/apps.py
@@ -2,8 +2,9 @@ from django.apps import AppConfig, apps
 
 from . import record
 
+
 class ActivityConfig(AppConfig):
-    name = 'funkwhale_api.activity'
+    name = "funkwhale_api.activity"
 
     def ready(self):
         super(ActivityConfig, self).ready()
diff --git a/api/funkwhale_api/activity/record.py b/api/funkwhale_api/activity/record.py
index fa55c0e852..3e34b1027f 100644
--- a/api/funkwhale_api/activity/record.py
+++ b/api/funkwhale_api/activity/record.py
@@ -2,37 +2,36 @@ import persisting_theory
 
 
 class ActivityRegistry(persisting_theory.Registry):
-    look_into = 'activities'
+    look_into = "activities"
 
     def _register_for_model(self, model, attr, value):
         key = model._meta.label
-        d = self.setdefault(key, {'consumers': []})
+        d = self.setdefault(key, {"consumers": []})
         d[attr] = value
 
     def register_serializer(self, serializer_class):
         model = serializer_class.Meta.model
-        self._register_for_model(model, 'serializer', serializer_class)
+        self._register_for_model(model, "serializer", serializer_class)
         return serializer_class
 
     def register_consumer(self, label):
         def decorator(func):
-            consumers = self[label]['consumers']
+            consumers = self[label]["consumers"]
             if func not in consumers:
                 consumers.append(func)
             return func
+
         return decorator
 
 
 registry = ActivityRegistry()
 
 
-
-
 def send(obj):
     conf = registry[obj.__class__._meta.label]
-    consumers = conf['consumers']
+    consumers = conf["consumers"]
     if not consumers:
         return
-    serializer = conf['serializer'](obj)
+    serializer = conf["serializer"](obj)
     for consumer in consumers:
         consumer(data=serializer.data, obj=obj)
diff --git a/api/funkwhale_api/activity/serializers.py b/api/funkwhale_api/activity/serializers.py
index fd9b185cf9..6df3a58705 100644
--- a/api/funkwhale_api/activity/serializers.py
+++ b/api/funkwhale_api/activity/serializers.py
@@ -4,8 +4,8 @@ from funkwhale_api.activity import record
 
 
 class ModelSerializer(serializers.ModelSerializer):
-    id = serializers.CharField(source='get_activity_url')
-    local_id = serializers.IntegerField(source='id')
+    id = serializers.CharField(source="get_activity_url")
+    local_id = serializers.IntegerField(source="id")
     # url = serializers.SerializerMethodField()
 
     def get_url(self, obj):
@@ -17,8 +17,7 @@ class AutoSerializer(serializers.Serializer):
     A serializer that will automatically use registered activity serializers
     to serialize an henerogeneous list of objects (favorites, listenings, etc.)
     """
+
     def to_representation(self, instance):
-        serializer = record.registry[instance._meta.label]['serializer'](
-            instance
-        )
+        serializer = record.registry[instance._meta.label]["serializer"](instance)
         return serializer.data
diff --git a/api/funkwhale_api/activity/utils.py b/api/funkwhale_api/activity/utils.py
index 46336930ef..236d23d88e 100644
--- a/api/funkwhale_api/activity/utils.py
+++ b/api/funkwhale_api/activity/utils.py
@@ -6,31 +6,25 @@ from funkwhale_api.history.models import Listening
 
 
 def combined_recent(limit, **kwargs):
-    datetime_field = kwargs.pop('datetime_field', 'creation_date')
-    source_querysets = {
-        qs.model._meta.label: qs for qs in kwargs.pop('querysets')
-    }
+    datetime_field = kwargs.pop("datetime_field", "creation_date")
+    source_querysets = {qs.model._meta.label: qs for qs in kwargs.pop("querysets")}
     querysets = {
         k: qs.annotate(
-            __type=models.Value(
-                qs.model._meta.label, output_field=models.CharField()
-            )
-        ).values('pk', datetime_field, '__type')
+            __type=models.Value(qs.model._meta.label, output_field=models.CharField())
+        ).values("pk", datetime_field, "__type")
         for k, qs in source_querysets.items()
     }
     _qs_list = list(querysets.values())
     union_qs = _qs_list[0].union(*_qs_list[1:])
     records = []
-    for row in union_qs.order_by('-{}'.format(datetime_field))[:limit]:
-        records.append({
-            'type': row['__type'],
-            'when': row[datetime_field],
-            'pk': row['pk']
-        })
+    for row in union_qs.order_by("-{}".format(datetime_field))[:limit]:
+        records.append(
+            {"type": row["__type"], "when": row[datetime_field], "pk": row["pk"]}
+        )
     # Now we bulk-load each object type in turn
     to_load = {}
     for record in records:
-        to_load.setdefault(record['type'], []).append(record['pk'])
+        to_load.setdefault(record["type"], []).append(record["pk"])
     fetched = {}
 
     for key, pks in to_load.items():
@@ -39,26 +33,19 @@ def combined_recent(limit, **kwargs):
 
     # Annotate 'records' with loaded objects
     for record in records:
-        record['object'] = fetched[(record['type'], record['pk'])]
+        record["object"] = fetched[(record["type"], record["pk"])]
     return records
 
 
 def get_activity(user, limit=20):
-    query = fields.privacy_level_query(
-        user, lookup_field='user__privacy_level')
+    query = fields.privacy_level_query(user, lookup_field="user__privacy_level")
     querysets = [
         Listening.objects.filter(query).select_related(
-            'track',
-            'user',
-            'track__artist',
-            'track__album__artist',
+            "track", "user", "track__artist", "track__album__artist"
         ),
         TrackFavorite.objects.filter(query).select_related(
-            'track',
-            'user',
-            'track__artist',
-            'track__album__artist',
+            "track", "user", "track__artist", "track__album__artist"
         ),
     ]
     records = combined_recent(limit=limit, querysets=querysets)
-    return [r['object'] for r in records]
+    return [r["object"] for r in records]
diff --git a/api/funkwhale_api/activity/views.py b/api/funkwhale_api/activity/views.py
index e66de1ccfd..f2f747d4d8 100644
--- a/api/funkwhale_api/activity/views.py
+++ b/api/funkwhale_api/activity/views.py
@@ -17,4 +17,4 @@ class ActivityViewSet(viewsets.GenericViewSet):
     def list(self, request, *args, **kwargs):
         activity = utils.get_activity(user=request.user)
         serializer = self.serializer_class(activity, many=True)
-        return Response({'results': serializer.data}, status=200)
+        return Response({"results": serializer.data}, status=200)
diff --git a/api/funkwhale_api/common/auth.py b/api/funkwhale_api/common/auth.py
index faf13571d6..88010e798f 100644
--- a/api/funkwhale_api/common/auth.py
+++ b/api/funkwhale_api/common/auth.py
@@ -16,20 +16,19 @@ class TokenHeaderAuth(BaseJSONWebTokenAuthentication):
     def get_jwt_value(self, request):
 
         try:
-            qs = request.get('query_string', b'').decode('utf-8')
+            qs = request.get("query_string", b"").decode("utf-8")
             parsed = parse_qs(qs)
-            token = parsed['token'][0]
+            token = parsed["token"][0]
         except KeyError:
-            raise exceptions.AuthenticationFailed('No token')
+            raise exceptions.AuthenticationFailed("No token")
 
         if not token:
-            raise exceptions.AuthenticationFailed('Empty token')
+            raise exceptions.AuthenticationFailed("Empty token")
 
         return token
 
 
 class TokenAuthMiddleware:
-
     def __init__(self, inner):
         # Store the ASGI application we were passed
         self.inner = inner
@@ -41,5 +40,5 @@ class TokenAuthMiddleware:
         except (User.DoesNotExist, exceptions.AuthenticationFailed):
             user = AnonymousUser()
 
-        scope['user'] = user
+        scope["user"] = user
         return self.inner(scope)
diff --git a/api/funkwhale_api/common/authentication.py b/api/funkwhale_api/common/authentication.py
index c7566eac8b..9ddc06a3be 100644
--- a/api/funkwhale_api/common/authentication.py
+++ b/api/funkwhale_api/common/authentication.py
@@ -6,34 +6,34 @@ from rest_framework_jwt import authentication
 from rest_framework_jwt.settings import api_settings
 
 
-class JSONWebTokenAuthenticationQS(
-        authentication.BaseJSONWebTokenAuthentication):
+class JSONWebTokenAuthenticationQS(authentication.BaseJSONWebTokenAuthentication):
 
-    www_authenticate_realm = 'api'
+    www_authenticate_realm = "api"
 
     def get_jwt_value(self, request):
-        token = request.query_params.get('jwt')
-        if 'jwt' in request.query_params and not token:
-            msg = _('Invalid Authorization header. No credentials provided.')
+        token = request.query_params.get("jwt")
+        if "jwt" in request.query_params and not token:
+            msg = _("Invalid Authorization header. No credentials provided.")
             raise exceptions.AuthenticationFailed(msg)
         return token
 
     def authenticate_header(self, request):
         return '{0} realm="{1}"'.format(
-            api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm)
+            api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm
+        )
 
 
-class BearerTokenHeaderAuth(
-        authentication.BaseJSONWebTokenAuthentication):
+class BearerTokenHeaderAuth(authentication.BaseJSONWebTokenAuthentication):
     """
     For backward compatibility purpose, we used Authorization: JWT <token>
     but Authorization: Bearer <token> is probably better.
     """
-    www_authenticate_realm = 'api'
+
+    www_authenticate_realm = "api"
 
     def get_jwt_value(self, request):
         auth = authentication.get_authorization_header(request).split()
-        auth_header_prefix = 'bearer'
+        auth_header_prefix = "bearer"
 
         if not auth:
             if api_settings.JWT_AUTH_COOKIE:
@@ -44,14 +44,16 @@ class BearerTokenHeaderAuth(
             return None
 
         if len(auth) == 1:
-            msg = _('Invalid Authorization header. No credentials provided.')
+            msg = _("Invalid Authorization header. No credentials provided.")
             raise exceptions.AuthenticationFailed(msg)
         elif len(auth) > 2:
-            msg = _('Invalid Authorization header. Credentials string '
-                    'should not contain spaces.')
+            msg = _(
+                "Invalid Authorization header. Credentials string "
+                "should not contain spaces."
+            )
             raise exceptions.AuthenticationFailed(msg)
 
         return auth[1]
 
     def authenticate_header(self, request):
-        return '{0} realm="{1}"'.format('Bearer', self.www_authenticate_realm)
+        return '{0} realm="{1}"'.format("Bearer", self.www_authenticate_realm)
diff --git a/api/funkwhale_api/common/consumers.py b/api/funkwhale_api/common/consumers.py
index 300ce5e26e..32a8876dce 100644
--- a/api/funkwhale_api/common/consumers.py
+++ b/api/funkwhale_api/common/consumers.py
@@ -5,7 +5,7 @@ from funkwhale_api.common import channels
 class JsonAuthConsumer(JsonWebsocketConsumer):
     def connect(self):
         try:
-            assert self.scope['user'].pk is not None
+            assert self.scope["user"].pk is not None
         except (AssertionError, AttributeError, KeyError):
             return self.close()
 
diff --git a/api/funkwhale_api/common/dynamic_preferences_registry.py b/api/funkwhale_api/common/dynamic_preferences_registry.py
index 15b182671b..d6dfed7837 100644
--- a/api/funkwhale_api/common/dynamic_preferences_registry.py
+++ b/api/funkwhale_api/common/dynamic_preferences_registry.py
@@ -3,18 +3,19 @@ from dynamic_preferences.registries import global_preferences_registry
 
 from funkwhale_api.common import preferences
 
-common = types.Section('common')
+common = types.Section("common")
 
 
 @global_preferences_registry.register
 class APIAutenticationRequired(
-        preferences.DefaultFromSettingMixin, types.BooleanPreference):
+    preferences.DefaultFromSettingMixin, types.BooleanPreference
+):
     section = common
-    name = 'api_authentication_required'
-    verbose_name = 'API Requires authentication'
-    setting = 'API_AUTHENTICATION_REQUIRED'
+    name = "api_authentication_required"
+    verbose_name = "API Requires authentication"
+    setting = "API_AUTHENTICATION_REQUIRED"
     help_text = (
-        'If disabled, anonymous users will be able to query the API'
-        'and access music data (as well as other data exposed in the API '
-        'without specific permissions).'
+        "If disabled, anonymous users will be able to query the API"
+        "and access music data (as well as other data exposed in the API "
+        "without specific permissions)."
     )
diff --git a/api/funkwhale_api/common/fields.py b/api/funkwhale_api/common/fields.py
index 98e9716627..de3ee37e58 100644
--- a/api/funkwhale_api/common/fields.py
+++ b/api/funkwhale_api/common/fields.py
@@ -6,34 +6,31 @@ from funkwhale_api.music import utils
 
 
 PRIVACY_LEVEL_CHOICES = [
-    ('me', 'Only me'),
-    ('followers', 'Me and my followers'),
-    ('instance', 'Everyone on my instance, and my followers'),
-    ('everyone', 'Everyone, including people on other instances'),
+    ("me", "Only me"),
+    ("followers", "Me and my followers"),
+    ("instance", "Everyone on my instance, and my followers"),
+    ("everyone", "Everyone, including people on other instances"),
 ]
 
 
 def get_privacy_field():
     return models.CharField(
-        max_length=30, choices=PRIVACY_LEVEL_CHOICES, default='instance')
+        max_length=30, choices=PRIVACY_LEVEL_CHOICES, default="instance"
+    )
 
 
-def privacy_level_query(user, lookup_field='privacy_level'):
+def privacy_level_query(user, lookup_field="privacy_level"):
     if user.is_anonymous:
-        return models.Q(**{
-            lookup_field: 'everyone',
-        })
+        return models.Q(**{lookup_field: "everyone"})
 
-    return models.Q(**{
-        '{}__in'.format(lookup_field): [
-            'followers', 'instance', 'everyone'
-        ]
-    })
+    return models.Q(
+        **{"{}__in".format(lookup_field): ["followers", "instance", "everyone"]}
+    )
 
 
 class SearchFilter(django_filters.CharFilter):
     def __init__(self, *args, **kwargs):
-        self.search_fields = kwargs.pop('search_fields')
+        self.search_fields = kwargs.pop("search_fields")
         super().__init__(*args, **kwargs)
 
     def filter(self, qs, value):
diff --git a/api/funkwhale_api/common/management/commands/script.py b/api/funkwhale_api/common/management/commands/script.py
index 9d26a5836d..03e32d5dd7 100644
--- a/api/funkwhale_api/common/management/commands/script.py
+++ b/api/funkwhale_api/common/management/commands/script.py
@@ -4,17 +4,20 @@ from funkwhale_api.common import scripts
 
 
 class Command(BaseCommand):
-    help = 'Run a specific script from funkwhale_api/common/scripts/'
+    help = "Run a specific script from funkwhale_api/common/scripts/"
 
     def add_arguments(self, parser):
-        parser.add_argument('script_name', nargs='?', type=str)
+        parser.add_argument("script_name", nargs="?", type=str)
         parser.add_argument(
-            '--noinput', '--no-input', action='store_false', dest='interactive',
+            "--noinput",
+            "--no-input",
+            action="store_false",
+            dest="interactive",
             help="Do NOT prompt the user for input of any kind.",
         )
 
     def handle(self, *args, **options):
-        name = options['script_name']
+        name = options["script_name"]
         if not name:
             self.show_help()
 
@@ -23,44 +26,44 @@ class Command(BaseCommand):
             script = available_scripts[name]
         except KeyError:
             raise CommandError(
-                '{} is not a valid script. Run python manage.py script for a '
-                'list of available scripts'.format(name))
+                "{} is not a valid script. Run python manage.py script for a "
+                "list of available scripts".format(name)
+            )
 
-        self.stdout.write('')
-        if options['interactive']:
+        self.stdout.write("")
+        if options["interactive"]:
             message = (
-                'Are you sure you want to execute the script {}?\n\n'
+                "Are you sure you want to execute the script {}?\n\n"
                 "Type 'yes' to continue, or 'no' to cancel: "
             ).format(name)
-            if input(''.join(message)) != 'yes':
+            if input("".join(message)) != "yes":
                 raise CommandError("Script cancelled.")
-        script['entrypoint'](self, **options)
+        script["entrypoint"](self, **options)
 
     def show_help(self):
         indentation = 4
-        self.stdout.write('')
-        self.stdout.write('Available scripts:')
-        self.stdout.write('Launch with: python manage.py <script_name>')
+        self.stdout.write("")
+        self.stdout.write("Available scripts:")
+        self.stdout.write("Launch with: python manage.py <script_name>")
         available_scripts = self.get_scripts()
         for name, script in sorted(available_scripts.items()):
-            self.stdout.write('')
+            self.stdout.write("")
             self.stdout.write(self.style.SUCCESS(name))
-            self.stdout.write('')
-            for line in script['help'].splitlines():
-                self.stdout.write('     {}'.format(line))
-        self.stdout.write('')
+            self.stdout.write("")
+            for line in script["help"].splitlines():
+                self.stdout.write("     {}".format(line))
+        self.stdout.write("")
 
     def get_scripts(self):
         available_scripts = [
-            k for k in sorted(scripts.__dict__.keys())
-            if not k.startswith('__')
+            k for k in sorted(scripts.__dict__.keys()) if not k.startswith("__")
         ]
         data = {}
         for name in available_scripts:
             module = getattr(scripts, name)
             data[name] = {
-                'name': name,
-                'help': module.__doc__.strip(),
-                'entrypoint': module.main
+                "name": name,
+                "help": module.__doc__.strip(),
+                "entrypoint": module.main,
             }
         return data
diff --git a/api/funkwhale_api/common/migrations/0001_initial.py b/api/funkwhale_api/common/migrations/0001_initial.py
index e95cc11e9a..a362855b8c 100644
--- a/api/funkwhale_api/common/migrations/0001_initial.py
+++ b/api/funkwhale_api/common/migrations/0001_initial.py
@@ -7,6 +7,4 @@ class Migration(migrations.Migration):
 
     dependencies = []
 
-    operations = [
-        UnaccentExtension()
-    ]
+    operations = [UnaccentExtension()]
diff --git a/api/funkwhale_api/common/pagination.py b/api/funkwhale_api/common/pagination.py
index 20efcb7413..e5068bce20 100644
--- a/api/funkwhale_api/common/pagination.py
+++ b/api/funkwhale_api/common/pagination.py
@@ -2,5 +2,5 @@ from rest_framework.pagination import PageNumberPagination
 
 
 class FunkwhalePagination(PageNumberPagination):
-    page_size_query_param = 'page_size'
+    page_size_query_param = "page_size"
     max_page_size = 50
diff --git a/api/funkwhale_api/common/permissions.py b/api/funkwhale_api/common/permissions.py
index e9e8b8819f..82da2c5775 100644
--- a/api/funkwhale_api/common/permissions.py
+++ b/api/funkwhale_api/common/permissions.py
@@ -9,9 +9,8 @@ from funkwhale_api.common import preferences
 
 
 class ConditionalAuthentication(BasePermission):
-
     def has_permission(self, request, view):
-        if preferences.get('common__api_authentication_required'):
+        if preferences.get("common__api_authentication_required"):
             return request.user and request.user.is_authenticated
         return True
 
@@ -28,24 +27,25 @@ class OwnerPermission(BasePermission):
         owner_field = 'owner'
         owner_checks = ['read', 'write']
     """
+
     perms_map = {
-        'GET': 'read',
-        'OPTIONS': 'read',
-        'HEAD': 'read',
-        'POST': 'write',
-        'PUT': 'write',
-        'PATCH': 'write',
-        'DELETE': 'write',
+        "GET": "read",
+        "OPTIONS": "read",
+        "HEAD": "read",
+        "POST": "write",
+        "PUT": "write",
+        "PATCH": "write",
+        "DELETE": "write",
     }
 
     def has_object_permission(self, request, view, obj):
         method_check = self.perms_map[request.method]
-        owner_checks = getattr(view, 'owner_checks', ['read', 'write'])
+        owner_checks = getattr(view, "owner_checks", ["read", "write"])
         if method_check not in owner_checks:
             # check not enabled
             return True
 
-        owner_field = getattr(view, 'owner_field', 'user')
+        owner_field = getattr(view, "owner_field", "user")
         owner = operator.attrgetter(owner_field)(obj)
         if owner != request.user:
             raise Http404
diff --git a/api/funkwhale_api/common/preferences.py b/api/funkwhale_api/common/preferences.py
index a2d3f04b7f..9b8f12e856 100644
--- a/api/funkwhale_api/common/preferences.py
+++ b/api/funkwhale_api/common/preferences.py
@@ -17,7 +17,7 @@ def get(pref):
 
 
 class StringListSerializer(serializers.BaseSerializer):
-    separator = ','
+    separator = ","
     sort = True
 
     @classmethod
@@ -27,8 +27,8 @@ class StringListSerializer(serializers.BaseSerializer):
 
         if type(value) not in [list, tuple]:
             raise cls.exception(
-                "Cannot serialize, value {} is not a list or a tuple".format(
-                    value))
+                "Cannot serialize, value {} is not a list or a tuple".format(value)
+            )
 
         if cls.sort:
             value = sorted(value)
@@ -38,7 +38,7 @@ class StringListSerializer(serializers.BaseSerializer):
     def to_python(cls, value, **kwargs):
         if not value:
             return []
-        return value.split(',')
+        return value.split(",")
 
 
 class StringListPreference(types.BasePreferenceType):
@@ -47,5 +47,5 @@ class StringListPreference(types.BasePreferenceType):
 
     def get_api_additional_data(self):
         d = super(StringListPreference, self).get_api_additional_data()
-        d['choices'] = self.get('choices')
+        d["choices"] = self.get("choices")
         return d
diff --git a/api/funkwhale_api/common/scripts/django_permissions_to_user_permissions.py b/api/funkwhale_api/common/scripts/django_permissions_to_user_permissions.py
index 1bc971f809..1c20723851 100644
--- a/api/funkwhale_api/common/scripts/django_permissions_to_user_permissions.py
+++ b/api/funkwhale_api/common/scripts/django_permissions_to_user_permissions.py
@@ -8,22 +8,22 @@ from funkwhale_api.users import models
 from django.contrib.auth.models import Permission
 
 mapping = {
-    'dynamic_preferences.change_globalpreferencemodel': 'settings',
-    'music.add_importbatch': 'library',
-    'federation.change_library': 'federation',
+    "dynamic_preferences.change_globalpreferencemodel": "settings",
+    "music.add_importbatch": "library",
+    "federation.change_library": "federation",
 }
 
 
 def main(command, **kwargs):
     for codename, user_permission in sorted(mapping.items()):
-        app_label, c = codename.split('.')
-        p = Permission.objects.get(
-            content_type__app_label=app_label, codename=c)
+        app_label, c = codename.split(".")
+        p = Permission.objects.get(content_type__app_label=app_label, codename=c)
         users = models.User.objects.filter(
-            Q(groups__permissions=p) | Q(user_permissions=p)).distinct()
+            Q(groups__permissions=p) | Q(user_permissions=p)
+        ).distinct()
         total = users.count()
 
-        command.stdout.write('Updating {} users with {} permission...'.format(
-            total, user_permission
-        ))
-        users.update(**{'permission_{}'.format(user_permission): True})
+        command.stdout.write(
+            "Updating {} users with {} permission...".format(total, user_permission)
+        )
+        users.update(**{"permission_{}".format(user_permission): True})
diff --git a/api/funkwhale_api/common/scripts/test.py b/api/funkwhale_api/common/scripts/test.py
index ab401dca4a..b3a27f402a 100644
--- a/api/funkwhale_api/common/scripts/test.py
+++ b/api/funkwhale_api/common/scripts/test.py
@@ -5,4 +5,4 @@ You can launch it just to check how it works.
 
 
 def main(command, **kwargs):
-    command.stdout.write('Test script run successfully')
+    command.stdout.write("Test script run successfully")
diff --git a/api/funkwhale_api/common/serializers.py b/api/funkwhale_api/common/serializers.py
index a995cc360e..8cc85ec0c9 100644
--- a/api/funkwhale_api/common/serializers.py
+++ b/api/funkwhale_api/common/serializers.py
@@ -17,67 +17,68 @@ class ActionSerializer(serializers.Serializer):
     dangerous_actions = []
 
     def __init__(self, *args, **kwargs):
-        self.queryset = kwargs.pop('queryset')
+        self.queryset = kwargs.pop("queryset")
         if self.actions is None:
             raise ValueError(
-                'You must declare a list of actions on '
-                'the serializer class')
+                "You must declare a list of actions on " "the serializer class"
+            )
 
         for action in self.actions:
-            handler_name = 'handle_{}'.format(action)
-            assert hasattr(self, handler_name), (
-                '{} miss a {} method'.format(
-                    self.__class__.__name__, handler_name)
+            handler_name = "handle_{}".format(action)
+            assert hasattr(self, handler_name), "{} miss a {} method".format(
+                self.__class__.__name__, handler_name
             )
         super().__init__(self, *args, **kwargs)
 
     def validate_action(self, value):
         if value not in self.actions:
             raise serializers.ValidationError(
-                '{} is not a valid action. Pick one of {}.'.format(
-                    value, ', '.join(self.actions)
+                "{} is not a valid action. Pick one of {}.".format(
+                    value, ", ".join(self.actions)
                 )
             )
         return value
 
     def validate_objects(self, value):
         qs = None
-        if value == 'all':
-            return self.queryset.all().order_by('id')
+        if value == "all":
+            return self.queryset.all().order_by("id")
         if type(value) in [list, tuple]:
-            return self.queryset.filter(pk__in=value).order_by('id')
+            return self.queryset.filter(pk__in=value).order_by("id")
 
         raise serializers.ValidationError(
-            '{} is not a valid value for objects. You must provide either a '
-            'list of identifiers or the string "all".'.format(value))
+            "{} is not a valid value for objects. You must provide either a "
+            'list of identifiers or the string "all".'.format(value)
+        )
 
     def validate(self, data):
-        dangerous = data['action'] in self.dangerous_actions
-        if dangerous and self.initial_data['objects'] == 'all':
+        dangerous = data["action"] in self.dangerous_actions
+        if dangerous and self.initial_data["objects"] == "all":
             raise serializers.ValidationError(
-                'This action is to dangerous to be applied to all objects')
-        if self.filterset_class and 'filters' in data:
+                "This action is to dangerous to be applied to all objects"
+            )
+        if self.filterset_class and "filters" in data:
             qs_filterset = self.filterset_class(
-                data['filters'], queryset=data['objects'])
+                data["filters"], queryset=data["objects"]
+            )
             try:
                 assert qs_filterset.form.is_valid()
             except (AssertionError, TypeError):
-                raise serializers.ValidationError('Invalid filters')
-            data['objects'] = qs_filterset.qs
+                raise serializers.ValidationError("Invalid filters")
+            data["objects"] = qs_filterset.qs
 
-        data['count'] = data['objects'].count()
-        if data['count'] < 1:
-            raise serializers.ValidationError(
-                'No object matching your request')
+        data["count"] = data["objects"].count()
+        if data["count"] < 1:
+            raise serializers.ValidationError("No object matching your request")
         return data
 
     def save(self):
-        handler_name = 'handle_{}'.format(self.validated_data['action'])
+        handler_name = "handle_{}".format(self.validated_data["action"])
         handler = getattr(self, handler_name)
-        result = handler(self.validated_data['objects'])
+        result = handler(self.validated_data["objects"])
         payload = {
-            'updated': self.validated_data['count'],
-            'action': self.validated_data['action'],
-            'result': result,
+            "updated": self.validated_data["count"],
+            "action": self.validated_data["action"],
+            "result": result,
         }
         return payload
diff --git a/api/funkwhale_api/common/session.py b/api/funkwhale_api/common/session.py
index 7f5584bd1c..871fc4c219 100644
--- a/api/funkwhale_api/common/session.py
+++ b/api/funkwhale_api/common/session.py
@@ -6,13 +6,12 @@ import funkwhale_api
 
 
 def get_user_agent():
-    return 'python-requests (funkwhale/{}; +{})'.format(
-        funkwhale_api.__version__,
-        settings.FUNKWHALE_URL
+    return "python-requests (funkwhale/{}; +{})".format(
+        funkwhale_api.__version__, settings.FUNKWHALE_URL
     )
 
 
 def get_session():
     s = requests.Session()
-    s.headers['User-Agent'] = get_user_agent()
+    s.headers["User-Agent"] = get_user_agent()
     return s
diff --git a/api/funkwhale_api/common/storage.py b/api/funkwhale_api/common/storage.py
index 658ce795a4..c5651693f5 100644
--- a/api/funkwhale_api/common/storage.py
+++ b/api/funkwhale_api/common/storage.py
@@ -7,6 +7,7 @@ class ASCIIFileSystemStorage(FileSystemStorage):
     """
     Convert unicode characters in name to ASCII characters.
     """
+
     def get_valid_name(self, name):
-        name = unicodedata.normalize('NFKD', name).encode('ascii', 'ignore')
+        name = unicodedata.normalize("NFKD", name).encode("ascii", "ignore")
         return super().get_valid_name(name)
diff --git a/api/funkwhale_api/common/utils.py b/api/funkwhale_api/common/utils.py
index 2d7641bf56..f2ea52a8c5 100644
--- a/api/funkwhale_api/common/utils.py
+++ b/api/funkwhale_api/common/utils.py
@@ -9,13 +9,13 @@ def rename_file(instance, field_name, new_name, allow_missing_file=False):
     field = getattr(instance, field_name)
     current_name, extension = os.path.splitext(field.name)
 
-    new_name_with_extension = '{}{}'.format(new_name, extension)
+    new_name_with_extension = "{}{}".format(new_name, extension)
     try:
         shutil.move(field.path, new_name_with_extension)
     except FileNotFoundError:
         if not allow_missing_file:
             raise
-        print('Skipped missing file', field.path)
+        print("Skipped missing file", field.path)
     initial_path = os.path.dirname(field.name)
     field.name = os.path.join(initial_path, new_name_with_extension)
     instance.save()
@@ -23,9 +23,7 @@ def rename_file(instance, field_name, new_name, allow_missing_file=False):
 
 
 def on_commit(f, *args, **kwargs):
-    return transaction.on_commit(
-        lambda: f(*args, **kwargs)
-    )
+    return transaction.on_commit(lambda: f(*args, **kwargs))
 
 
 def set_query_parameter(url, **kwargs):
diff --git a/api/funkwhale_api/contrib/sites/migrations/0001_initial.py b/api/funkwhale_api/contrib/sites/migrations/0001_initial.py
index cf95cec658..8b7ec088cb 100644
--- a/api/funkwhale_api/contrib/sites/migrations/0001_initial.py
+++ b/api/funkwhale_api/contrib/sites/migrations/0001_initial.py
@@ -7,25 +7,39 @@ import django.contrib.sites.models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-    ]
+    dependencies = []
 
     operations = [
         migrations.CreateModel(
-            name='Site',
+            name="Site",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
-                ('domain', models.CharField(verbose_name='domain name', max_length=100, validators=[django.contrib.sites.models._simple_domain_name_validator])),
-                ('name', models.CharField(verbose_name='display name', max_length=50)),
+                (
+                    "id",
+                    models.AutoField(
+                        verbose_name="ID",
+                        primary_key=True,
+                        serialize=False,
+                        auto_created=True,
+                    ),
+                ),
+                (
+                    "domain",
+                    models.CharField(
+                        verbose_name="domain name",
+                        max_length=100,
+                        validators=[
+                            django.contrib.sites.models._simple_domain_name_validator
+                        ],
+                    ),
+                ),
+                ("name", models.CharField(verbose_name="display name", max_length=50)),
             ],
             options={
-                'verbose_name_plural': 'sites',
-                'verbose_name': 'site',
-                'db_table': 'django_site',
-                'ordering': ('domain',),
+                "verbose_name_plural": "sites",
+                "verbose_name": "site",
+                "db_table": "django_site",
+                "ordering": ("domain",),
             },
-            managers=[
-                ('objects', django.contrib.sites.models.SiteManager()),
-            ],
-        ),
+            managers=[("objects", django.contrib.sites.models.SiteManager())],
+        )
     ]
diff --git a/api/funkwhale_api/contrib/sites/migrations/0002_set_site_domain_and_name.py b/api/funkwhale_api/contrib/sites/migrations/0002_set_site_domain_and_name.py
index e92c8c338f..7b091708c4 100644
--- a/api/funkwhale_api/contrib/sites/migrations/0002_set_site_domain_and_name.py
+++ b/api/funkwhale_api/contrib/sites/migrations/0002_set_site_domain_and_name.py
@@ -10,10 +10,7 @@ def update_site_forward(apps, schema_editor):
     Site = apps.get_model("sites", "Site")
     Site.objects.update_or_create(
         id=settings.SITE_ID,
-        defaults={
-            "domain": "funkwhale.io",
-            "name": "funkwhale_api"
-        }
+        defaults={"domain": "funkwhale.io", "name": "funkwhale_api"},
     )
 
 
@@ -21,20 +18,12 @@ def update_site_backward(apps, schema_editor):
     """Revert site domain and name to default."""
     Site = apps.get_model("sites", "Site")
     Site.objects.update_or_create(
-        id=settings.SITE_ID,
-        defaults={
-            "domain": "example.com",
-            "name": "example.com"
-        }
+        id=settings.SITE_ID, defaults={"domain": "example.com", "name": "example.com"}
     )
 
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('sites', '0001_initial'),
-    ]
+    dependencies = [("sites", "0001_initial")]
 
-    operations = [
-        migrations.RunPython(update_site_forward, update_site_backward),
-    ]
+    operations = [migrations.RunPython(update_site_forward, update_site_backward)]
diff --git a/api/funkwhale_api/contrib/sites/migrations/0003_auto_20171214_2205.py b/api/funkwhale_api/contrib/sites/migrations/0003_auto_20171214_2205.py
index 14a9ec1a8a..5a903b8d19 100644
--- a/api/funkwhale_api/contrib/sites/migrations/0003_auto_20171214_2205.py
+++ b/api/funkwhale_api/contrib/sites/migrations/0003_auto_20171214_2205.py
@@ -8,20 +8,21 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('sites', '0002_set_site_domain_and_name'),
-    ]
+    dependencies = [("sites", "0002_set_site_domain_and_name")]
 
     operations = [
         migrations.AlterModelManagers(
-            name='site',
-            managers=[
-                ('objects', django.contrib.sites.models.SiteManager()),
-            ],
+            name="site",
+            managers=[("objects", django.contrib.sites.models.SiteManager())],
         ),
         migrations.AlterField(
-            model_name='site',
-            name='domain',
-            field=models.CharField(max_length=100, unique=True, validators=[django.contrib.sites.models._simple_domain_name_validator], verbose_name='domain name'),
+            model_name="site",
+            name="domain",
+            field=models.CharField(
+                max_length=100,
+                unique=True,
+                validators=[django.contrib.sites.models._simple_domain_name_validator],
+                verbose_name="domain name",
+            ),
         ),
     ]
diff --git a/api/funkwhale_api/downloader/downloader.py b/api/funkwhale_api/downloader/downloader.py
index 7fc237b089..3599b86ef1 100644
--- a/api/funkwhale_api/downloader/downloader.py
+++ b/api/funkwhale_api/downloader/downloader.py
@@ -7,20 +7,15 @@ import glob
 
 
 def download(
-        url,
-        target_directory=settings.MEDIA_ROOT,
-        name="%(id)s.%(ext)s",
-        bitrate=192):
+    url, target_directory=settings.MEDIA_ROOT, name="%(id)s.%(ext)s", bitrate=192
+):
     target_path = os.path.join(target_directory, name)
     ydl_opts = {
-        'quiet': True,
-        'outtmpl': target_path,
-        'postprocessors': [{
-            'key': 'FFmpegExtractAudio',
-            'preferredcodec': 'vorbis',
-        }],
+        "quiet": True,
+        "outtmpl": target_path,
+        "postprocessors": [{"key": "FFmpegExtractAudio", "preferredcodec": "vorbis"}],
     }
     _downloader = youtube_dl.YoutubeDL(ydl_opts)
     info = _downloader.extract_info(url)
-    info['audio_file_path'] = target_path % {'id': info['id'], 'ext': 'ogg'}
+    info["audio_file_path"] = target_path % {"id": info["id"], "ext": "ogg"}
     return info
diff --git a/api/funkwhale_api/factories.py b/api/funkwhale_api/factories.py
index 6fed66edb2..602037a065 100644
--- a/api/funkwhale_api/factories.py
+++ b/api/funkwhale_api/factories.py
@@ -3,7 +3,7 @@ import persisting_theory
 
 
 class FactoriesRegistry(persisting_theory.Registry):
-    look_into = 'factories'
+    look_into = "factories"
 
     def prepare_name(self, data, name=None):
         return name or data._meta.model._meta.label
diff --git a/api/funkwhale_api/favorites/activities.py b/api/funkwhale_api/favorites/activities.py
index a2dbc4e2fa..d9d546335b 100644
--- a/api/funkwhale_api/favorites/activities.py
+++ b/api/funkwhale_api/favorites/activities.py
@@ -3,17 +3,14 @@ from funkwhale_api.activity import record
 
 from . import serializers
 
-record.registry.register_serializer(
-    serializers.TrackFavoriteActivitySerializer)
+record.registry.register_serializer(serializers.TrackFavoriteActivitySerializer)
 
 
-@record.registry.register_consumer('favorites.TrackFavorite')
+@record.registry.register_consumer("favorites.TrackFavorite")
 def broadcast_track_favorite_to_instance_activity(data, obj):
-    if obj.user.privacy_level not in ['instance', 'everyone']:
+    if obj.user.privacy_level not in ["instance", "everyone"]:
         return
 
-    channels.group_send('instance_activity', {
-        'type': 'event.send',
-        'text': '',
-        'data': data
-    })
+    channels.group_send(
+        "instance_activity", {"type": "event.send", "text": "", "data": data}
+    )
diff --git a/api/funkwhale_api/favorites/admin.py b/api/funkwhale_api/favorites/admin.py
index e8f29fac45..f56980e8cc 100644
--- a/api/funkwhale_api/favorites/admin.py
+++ b/api/funkwhale_api/favorites/admin.py
@@ -5,8 +5,5 @@ from . import models
 
 @admin.register(models.TrackFavorite)
 class TrackFavoriteAdmin(admin.ModelAdmin):
-    list_display = ['user', 'track', 'creation_date']
-    list_select_related = [
-        'user',
-        'track'
-    ]
+    list_display = ["user", "track", "creation_date"]
+    list_select_related = ["user", "track"]
diff --git a/api/funkwhale_api/favorites/factories.py b/api/funkwhale_api/favorites/factories.py
index 233dd049c5..797c135ef0 100644
--- a/api/funkwhale_api/favorites/factories.py
+++ b/api/funkwhale_api/favorites/factories.py
@@ -12,4 +12,4 @@ class TrackFavorite(factory.django.DjangoModelFactory):
     user = factory.SubFactory(UserFactory)
 
     class Meta:
-        model = 'favorites.TrackFavorite'
+        model = "favorites.TrackFavorite"
diff --git a/api/funkwhale_api/favorites/migrations/0001_initial.py b/api/funkwhale_api/favorites/migrations/0001_initial.py
index c2bd03182d..17a66462e1 100644
--- a/api/funkwhale_api/favorites/migrations/0001_initial.py
+++ b/api/funkwhale_api/favorites/migrations/0001_initial.py
@@ -9,25 +9,47 @@ from django.conf import settings
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('music', '0003_auto_20151222_2233'),
+        ("music", "0003_auto_20151222_2233"),
         migrations.swappable_dependency(settings.AUTH_USER_MODEL),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='TrackFavorite',
+            name="TrackFavorite",
             fields=[
-                ('id', models.AutoField(serialize=False, auto_created=True, verbose_name='ID', primary_key=True)),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('track', models.ForeignKey(related_name='track_favorites', to='music.Track', on_delete=models.CASCADE)),
-                ('user', models.ForeignKey(related_name='track_favorites', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
+                (
+                    "id",
+                    models.AutoField(
+                        serialize=False,
+                        auto_created=True,
+                        verbose_name="ID",
+                        primary_key=True,
+                    ),
+                ),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                (
+                    "track",
+                    models.ForeignKey(
+                        related_name="track_favorites",
+                        to="music.Track",
+                        on_delete=models.CASCADE,
+                    ),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        related_name="track_favorites",
+                        to=settings.AUTH_USER_MODEL,
+                        on_delete=models.CASCADE,
+                    ),
+                ),
             ],
-            options={
-                'ordering': ('-creation_date',),
-            },
+            options={"ordering": ("-creation_date",)},
         ),
         migrations.AlterUniqueTogether(
-            name='trackfavorite',
-            unique_together=set([('track', 'user')]),
+            name="trackfavorite", unique_together=set([("track", "user")])
         ),
     ]
diff --git a/api/funkwhale_api/favorites/models.py b/api/funkwhale_api/favorites/models.py
index 0c6a6b11c6..dd2b8f3ec9 100644
--- a/api/funkwhale_api/favorites/models.py
+++ b/api/funkwhale_api/favorites/models.py
@@ -8,13 +8,15 @@ from funkwhale_api.music.models import Track
 class TrackFavorite(models.Model):
     creation_date = models.DateTimeField(default=timezone.now)
     user = models.ForeignKey(
-        'users.User', related_name='track_favorites', on_delete=models.CASCADE)
+        "users.User", related_name="track_favorites", on_delete=models.CASCADE
+    )
     track = models.ForeignKey(
-        Track, related_name='track_favorites', on_delete=models.CASCADE)
+        Track, related_name="track_favorites", on_delete=models.CASCADE
+    )
 
     class Meta:
-        unique_together = ('track', 'user')
-        ordering = ('-creation_date',)
+        unique_together = ("track", "user")
+        ordering = ("-creation_date",)
 
     @classmethod
     def add(cls, track, user):
@@ -22,5 +24,4 @@ class TrackFavorite(models.Model):
         return favorite
 
     def get_activity_url(self):
-        return '{}/favorites/tracks/{}'.format(
-            self.user.get_activity_url(), self.pk)
+        return "{}/favorites/tracks/{}".format(self.user.get_activity_url(), self.pk)
diff --git a/api/funkwhale_api/favorites/serializers.py b/api/funkwhale_api/favorites/serializers.py
index bb4538b2d9..40260c2aa9 100644
--- a/api/funkwhale_api/favorites/serializers.py
+++ b/api/funkwhale_api/favorites/serializers.py
@@ -11,29 +11,22 @@ from . import models
 
 class TrackFavoriteActivitySerializer(activity_serializers.ModelSerializer):
     type = serializers.SerializerMethodField()
-    object = TrackActivitySerializer(source='track')
-    actor = UserActivitySerializer(source='user')
-    published = serializers.DateTimeField(source='creation_date')
+    object = TrackActivitySerializer(source="track")
+    actor = UserActivitySerializer(source="user")
+    published = serializers.DateTimeField(source="creation_date")
 
     class Meta:
         model = models.TrackFavorite
-        fields = [
-            'id',
-            'local_id',
-            'object',
-            'type',
-            'actor',
-            'published'
-        ]
+        fields = ["id", "local_id", "object", "type", "actor", "published"]
 
     def get_actor(self, obj):
         return UserActivitySerializer(obj.user).data
 
     def get_type(self, obj):
-        return 'Like'
+        return "Like"
 
 
 class UserTrackFavoriteSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.TrackFavorite
-        fields = ('id', 'track', 'creation_date')
+        fields = ("id", "track", "creation_date")
diff --git a/api/funkwhale_api/favorites/urls.py b/api/funkwhale_api/favorites/urls.py
index 6a9b12a81e..b85023adea 100644
--- a/api/funkwhale_api/favorites/urls.py
+++ b/api/funkwhale_api/favorites/urls.py
@@ -2,7 +2,8 @@ from django.conf.urls import include, url
 from . import views
 
 from rest_framework import routers
+
 router = routers.SimpleRouter()
-router.register(r'tracks', views.TrackFavoriteViewSet, 'tracks')
+router.register(r"tracks", views.TrackFavoriteViewSet, "tracks")
 
 urlpatterns = router.urls
diff --git a/api/funkwhale_api/favorites/views.py b/api/funkwhale_api/favorites/views.py
index cd2aa3b61c..7deae7a984 100644
--- a/api/funkwhale_api/favorites/views.py
+++ b/api/funkwhale_api/favorites/views.py
@@ -12,13 +12,15 @@ from . import models
 from . import serializers
 
 
-class TrackFavoriteViewSet(mixins.CreateModelMixin,
-                           mixins.DestroyModelMixin,
-                           mixins.ListModelMixin,
-                           viewsets.GenericViewSet):
+class TrackFavoriteViewSet(
+    mixins.CreateModelMixin,
+    mixins.DestroyModelMixin,
+    mixins.ListModelMixin,
+    viewsets.GenericViewSet,
+):
 
     serializer_class = serializers.UserTrackFavoriteSerializer
-    queryset = (models.TrackFavorite.objects.all())
+    queryset = models.TrackFavorite.objects.all()
     permission_classes = [ConditionalAuthentication]
 
     def create(self, request, *args, **kwargs):
@@ -28,20 +30,22 @@ class TrackFavoriteViewSet(mixins.CreateModelMixin,
         serializer = self.get_serializer(instance=instance)
         headers = self.get_success_headers(serializer.data)
         record.send(instance)
-        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
+        return Response(
+            serializer.data, status=status.HTTP_201_CREATED, headers=headers
+        )
 
     def get_queryset(self):
         return self.queryset.filter(user=self.request.user)
 
     def perform_create(self, serializer):
-        track = Track.objects.get(pk=serializer.data['track'])
+        track = Track.objects.get(pk=serializer.data["track"])
         favorite = models.TrackFavorite.add(track=track, user=self.request.user)
         return favorite
 
-    @list_route(methods=['delete', 'post'])
+    @list_route(methods=["delete", "post"])
     def remove(self, request, *args, **kwargs):
         try:
-            pk = int(request.data['track'])
+            pk = int(request.data["track"])
             favorite = request.user.track_favorites.get(track__pk=pk)
         except (AttributeError, ValueError, models.TrackFavorite.DoesNotExist):
             return Response({}, status=400)
diff --git a/api/funkwhale_api/federation/activity.py b/api/funkwhale_api/federation/activity.py
index becf6c96f5..6dd26a87dc 100644
--- a/api/funkwhale_api/federation/activity.py
+++ b/api/funkwhale_api/federation/activity.py
@@ -2,66 +2,59 @@ from . import serializers
 from . import tasks
 
 ACTIVITY_TYPES = [
-    'Accept',
-    'Add',
-    'Announce',
-    'Arrive',
-    'Block',
-    'Create',
-    'Delete',
-    'Dislike',
-    'Flag',
-    'Follow',
-    'Ignore',
-    'Invite',
-    'Join',
-    'Leave',
-    'Like',
-    'Listen',
-    'Move',
-    'Offer',
-    'Question',
-    'Reject',
-    'Read',
-    'Remove',
-    'TentativeReject',
-    'TentativeAccept',
-    'Travel',
-    'Undo',
-    'Update',
-    'View',
+    "Accept",
+    "Add",
+    "Announce",
+    "Arrive",
+    "Block",
+    "Create",
+    "Delete",
+    "Dislike",
+    "Flag",
+    "Follow",
+    "Ignore",
+    "Invite",
+    "Join",
+    "Leave",
+    "Like",
+    "Listen",
+    "Move",
+    "Offer",
+    "Question",
+    "Reject",
+    "Read",
+    "Remove",
+    "TentativeReject",
+    "TentativeAccept",
+    "Travel",
+    "Undo",
+    "Update",
+    "View",
 ]
 
 
 OBJECT_TYPES = [
-    'Article',
-    'Audio',
-    'Collection',
-    'Document',
-    'Event',
-    'Image',
-    'Note',
-    'OrderedCollection',
-    'Page',
-    'Place',
-    'Profile',
-    'Relationship',
-    'Tombstone',
-    'Video',
+    "Article",
+    "Audio",
+    "Collection",
+    "Document",
+    "Event",
+    "Image",
+    "Note",
+    "OrderedCollection",
+    "Page",
+    "Place",
+    "Profile",
+    "Relationship",
+    "Tombstone",
+    "Video",
 ] + ACTIVITY_TYPES
 
 
 def deliver(activity, on_behalf_of, to=[]):
-    return tasks.send.delay(
-        activity=activity,
-        actor_id=on_behalf_of.pk,
-        to=to
-    )
+    return tasks.send.delay(activity=activity, actor_id=on_behalf_of.pk, to=to)
 
 
 def accept_follow(follow):
     serializer = serializers.AcceptFollowSerializer(follow)
-    return deliver(
-        serializer.data,
-        to=[follow.actor.url],
-        on_behalf_of=follow.target)
+    return deliver(serializer.data, to=[follow.actor.url], on_behalf_of=follow.target)
diff --git a/api/funkwhale_api/federation/actors.py b/api/funkwhale_api/federation/actors.py
index 7a209b1ff4..32b5456566 100644
--- a/api/funkwhale_api/federation/actors.py
+++ b/api/funkwhale_api/federation/actors.py
@@ -29,8 +29,10 @@ logger = logging.getLogger(__name__)
 
 
 def remove_tags(text):
-    logger.debug('Removing tags from %s', text)
-    return ''.join(xml.etree.ElementTree.fromstring('<div>{}</div>'.format(text)).itertext())
+    logger.debug("Removing tags from %s", text)
+    return "".join(
+        xml.etree.ElementTree.fromstring("<div>{}</div>".format(text)).itertext()
+    )
 
 
 def get_actor_data(actor_url):
@@ -38,16 +40,13 @@ def get_actor_data(actor_url):
         actor_url,
         timeout=5,
         verify=settings.EXTERNAL_REQUESTS_VERIFY_SSL,
-        headers={
-            'Accept': 'application/activity+json',
-        }
+        headers={"Accept": "application/activity+json"},
     )
     response.raise_for_status()
     try:
         return response.json()
     except:
-        raise ValueError(
-            'Invalid actor payload: {}'.format(response.text))
+        raise ValueError("Invalid actor payload: {}".format(response.text))
 
 
 def get_actor(actor_url):
@@ -56,7 +55,8 @@ def get_actor(actor_url):
     except models.Actor.DoesNotExist:
         actor = None
     fetch_delta = datetime.timedelta(
-        minutes=preferences.get('federation__actor_fetch_delay'))
+        minutes=preferences.get("federation__actor_fetch_delay")
+    )
     if actor and actor.last_fetch_date > timezone.now() - fetch_delta:
         # cache is hot, we can return as is
         return actor
@@ -73,8 +73,7 @@ class SystemActor(object):
 
     def get_request_auth(self):
         actor = self.get_actor_instance()
-        return signing.get_auth(
-            actor.private_key, actor.private_key_id)
+        return signing.get_auth(actor.private_key, actor.private_key_id)
 
     def serialize(self):
         actor = self.get_actor_instance()
@@ -88,42 +87,35 @@ class SystemActor(object):
             pass
         private, public = keys.get_key_pair()
         args = self.get_instance_argument(
-            self.id,
-            name=self.name,
-            summary=self.summary,
-            **self.additional_attributes
+            self.id, name=self.name, summary=self.summary, **self.additional_attributes
         )
-        args['private_key'] = private.decode('utf-8')
-        args['public_key'] = public.decode('utf-8')
+        args["private_key"] = private.decode("utf-8")
+        args["public_key"] = public.decode("utf-8")
         return models.Actor.objects.create(**args)
 
     def get_actor_url(self):
         return utils.full_url(
-            reverse(
-                'federation:instance-actors-detail',
-                kwargs={'actor': self.id}))
+            reverse("federation:instance-actors-detail", kwargs={"actor": self.id})
+        )
 
     def get_instance_argument(self, id, name, summary, **kwargs):
         p = {
-            'preferred_username': id,
-            'domain': settings.FEDERATION_HOSTNAME,
-            'type': 'Person',
-            'name': name.format(host=settings.FEDERATION_HOSTNAME),
-            'manually_approves_followers': True,
-            'url': self.get_actor_url(),
-            'shared_inbox_url': utils.full_url(
-                reverse(
-                    'federation:instance-actors-inbox',
-                    kwargs={'actor': id})),
-            'inbox_url': utils.full_url(
-                reverse(
-                    'federation:instance-actors-inbox',
-                    kwargs={'actor': id})),
-            'outbox_url': utils.full_url(
-                reverse(
-                    'federation:instance-actors-outbox',
-                    kwargs={'actor': id})),
-            'summary': summary.format(host=settings.FEDERATION_HOSTNAME)
+            "preferred_username": id,
+            "domain": settings.FEDERATION_HOSTNAME,
+            "type": "Person",
+            "name": name.format(host=settings.FEDERATION_HOSTNAME),
+            "manually_approves_followers": True,
+            "url": self.get_actor_url(),
+            "shared_inbox_url": utils.full_url(
+                reverse("federation:instance-actors-inbox", kwargs={"actor": id})
+            ),
+            "inbox_url": utils.full_url(
+                reverse("federation:instance-actors-inbox", kwargs={"actor": id})
+            ),
+            "outbox_url": utils.full_url(
+                reverse("federation:instance-actors-outbox", kwargs={"actor": id})
+            ),
+            "summary": summary.format(host=settings.FEDERATION_HOSTNAME),
         }
         p.update(kwargs)
         return p
@@ -145,22 +137,19 @@ class SystemActor(object):
         Main entrypoint for handling activities posted to the
         actor's inbox
         """
-        logger.info('Received activity on %s inbox', self.id)
+        logger.info("Received activity on %s inbox", self.id)
 
         if actor is None:
-            raise PermissionDenied('Actor not authenticated')
+            raise PermissionDenied("Actor not authenticated")
 
-        serializer = serializers.ActivitySerializer(
-            data=data, context={'actor': actor})
+        serializer = serializers.ActivitySerializer(data=data, context={"actor": actor})
         serializer.is_valid(raise_exception=True)
 
         ac = serializer.data
         try:
-            handler = getattr(
-                self, 'handle_{}'.format(ac['type'].lower()))
+            handler = getattr(self, "handle_{}".format(ac["type"].lower()))
         except (KeyError, AttributeError):
-            logger.debug(
-                'No handler for activity %s', ac['type'])
+            logger.debug("No handler for activity %s", ac["type"])
             return
 
         return handler(data, actor)
@@ -168,9 +157,10 @@ class SystemActor(object):
     def handle_follow(self, ac, sender):
         system_actor = self.get_actor_instance()
         serializer = serializers.FollowSerializer(
-            data=ac, context={'follow_actor': sender})
+            data=ac, context={"follow_actor": sender}
+        )
         if not serializer.is_valid():
-            return logger.info('Invalid follow payload')
+            return logger.info("Invalid follow payload")
         approved = True if not self.manually_approves_followers else None
         follow = serializer.save(approved=approved)
         if follow.approved:
@@ -179,26 +169,27 @@ class SystemActor(object):
     def handle_accept(self, ac, sender):
         system_actor = self.get_actor_instance()
         serializer = serializers.AcceptFollowSerializer(
-            data=ac,
-            context={'follow_target': sender, 'follow_actor': system_actor})
+            data=ac, context={"follow_target": sender, "follow_actor": system_actor}
+        )
         if not serializer.is_valid(raise_exception=True):
-            return logger.info('Received invalid payload')
+            return logger.info("Received invalid payload")
 
         return serializer.save()
 
     def handle_undo_follow(self, ac, sender):
         system_actor = self.get_actor_instance()
         serializer = serializers.UndoFollowSerializer(
-            data=ac, context={'actor': sender, 'target': system_actor})
+            data=ac, context={"actor": sender, "target": system_actor}
+        )
         if not serializer.is_valid():
-            return logger.info('Received invalid payload')
+            return logger.info("Received invalid payload")
         serializer.save()
 
     def handle_undo(self, ac, sender):
-        if ac['object']['type'] != 'Follow':
+        if ac["object"]["type"] != "Follow":
             return
 
-        if ac['object']['actor'] != sender.url:
+        if ac["object"]["actor"] != sender.url:
             # not the same actor, permission issue
             return
 
@@ -206,55 +197,52 @@ class SystemActor(object):
 
 
 class LibraryActor(SystemActor):
-    id = 'library'
-    name = '{host}\'s library'
-    summary = 'Bot account to federate with {host}\'s library'
-    additional_attributes = {
-        'manually_approves_followers': True
-    }
+    id = "library"
+    name = "{host}'s library"
+    summary = "Bot account to federate with {host}'s library"
+    additional_attributes = {"manually_approves_followers": True}
 
     def serialize(self):
         data = super().serialize()
-        urls = data.setdefault('url', [])
-        urls.append({
-            'type': 'Link',
-            'mediaType': 'application/activity+json',
-            'name': 'library',
-            'href': utils.full_url(reverse('federation:music:files-list'))
-        })
+        urls = data.setdefault("url", [])
+        urls.append(
+            {
+                "type": "Link",
+                "mediaType": "application/activity+json",
+                "name": "library",
+                "href": utils.full_url(reverse("federation:music:files-list")),
+            }
+        )
         return data
 
     @property
     def manually_approves_followers(self):
-        return preferences.get('federation__music_needs_approval')
+        return preferences.get("federation__music_needs_approval")
 
     @transaction.atomic
     def handle_create(self, ac, sender):
         try:
             remote_library = models.Library.objects.get(
-                actor=sender,
-                federation_enabled=True,
+                actor=sender, federation_enabled=True
             )
         except models.Library.DoesNotExist:
-            logger.info(
-                'Skipping import, we\'re not following %s', sender.url)
+            logger.info("Skipping import, we're not following %s", sender.url)
             return
 
-        if ac['object']['type'] != 'Collection':
+        if ac["object"]["type"] != "Collection":
             return
 
-        if ac['object']['totalItems'] <= 0:
+        if ac["object"]["totalItems"] <= 0:
             return
 
         try:
-            items = ac['object']['items']
+            items = ac["object"]["items"]
         except KeyError:
-            logger.warning('No items in collection!')
+            logger.warning("No items in collection!")
             return
 
         item_serializers = [
-            serializers.AudioSerializer(
-                data=i, context={'library': remote_library})
+            serializers.AudioSerializer(data=i, context={"library": remote_library})
             for i in items
         ]
         now = timezone.now()
@@ -263,27 +251,21 @@ class LibraryActor(SystemActor):
             if s.is_valid():
                 valid_serializers.append(s)
             else:
-                logger.debug(
-                    'Skipping invalid item %s, %s', s.initial_data, s.errors)
+                logger.debug("Skipping invalid item %s, %s", s.initial_data, s.errors)
 
         lts = []
         for s in valid_serializers:
             lts.append(s.save())
 
         if remote_library.autoimport:
-            batch = music_models.ImportBatch.objects.create(
-                source='federation',
-            )
+            batch = music_models.ImportBatch.objects.create(source="federation")
             for lt in lts:
                 if lt.creation_date < now:
                     # track was already in the library, we do not trigger
                     # an import
                     continue
                 job = music_models.ImportJob.objects.create(
-                    batch=batch,
-                    library_track=lt,
-                    mbid=lt.mbid,
-                    source=lt.url,
+                    batch=batch, library_track=lt, mbid=lt.mbid, source=lt.url
                 )
                 funkwhale_utils.on_commit(
                     music_tasks.import_job_run.delay,
@@ -293,15 +275,13 @@ class LibraryActor(SystemActor):
 
 
 class TestActor(SystemActor):
-    id = 'test'
-    name = '{host}\'s test account'
+    id = "test"
+    name = "{host}'s test account"
     summary = (
-        'Bot account to test federation with {host}. '
-        'Send me /ping and I\'ll answer you.'
+        "Bot account to test federation with {host}. "
+        "Send me /ping and I'll answer you."
     )
-    additional_attributes = {
-        'manually_approves_followers': False
-    }
+    additional_attributes = {"manually_approves_followers": False}
     manually_approves_followers = False
 
     def get_outbox(self, data, actor=None):
@@ -309,15 +289,14 @@ class TestActor(SystemActor):
             "@context": [
                 "https://www.w3.org/ns/activitystreams",
                 "https://w3id.org/security/v1",
-                {}
+                {},
             ],
             "id": utils.full_url(
-                reverse(
-                    'federation:instance-actors-outbox',
-                    kwargs={'actor': self.id})),
+                reverse("federation:instance-actors-outbox", kwargs={"actor": self.id})
+            ),
             "type": "OrderedCollection",
             "totalItems": 0,
-            "orderedItems": []
+            "orderedItems": [],
         }
 
     def parse_command(self, message):
@@ -327,99 +306,86 @@ class TestActor(SystemActor):
         """
         raw = remove_tags(message)
         try:
-            return raw.split('/')[1]
+            return raw.split("/")[1]
         except IndexError:
             return
 
     def handle_create(self, ac, sender):
-        if ac['object']['type'] != 'Note':
+        if ac["object"]["type"] != "Note":
             return
 
         # we received a toot \o/
-        command = self.parse_command(ac['object']['content'])
-        logger.debug('Parsed command: %s', command)
-        if command != 'ping':
+        command = self.parse_command(ac["object"]["content"])
+        logger.debug("Parsed command: %s", command)
+        if command != "ping":
             return
 
         now = timezone.now()
         test_actor = self.get_actor_instance()
-        reply_url = 'https://{}/activities/note/{}'.format(
+        reply_url = "https://{}/activities/note/{}".format(
             settings.FEDERATION_HOSTNAME, now.timestamp()
         )
-        reply_content = '{} Pong!'.format(
-            sender.mention_username
-        )
+        reply_content = "{} Pong!".format(sender.mention_username)
         reply_activity = {
             "@context": [
                 "https://www.w3.org/ns/activitystreams",
                 "https://w3id.org/security/v1",
-                {}
+                {},
             ],
-            'type': 'Create',
-            'actor': test_actor.url,
-            'id': '{}/activity'.format(reply_url),
-            'published': now.isoformat(),
-            'to': ac['actor'],
-            'cc': [],
-            'object': {
-                'type': 'Note',
-                'content': 'Pong!',
-                'summary': None,
-                'published': now.isoformat(),
-                'id': reply_url,
-                'inReplyTo': ac['object']['id'],
-                'sensitive': False,
-                'url': reply_url,
-                'to': [ac['actor']],
-                'attributedTo': test_actor.url,
-                'cc': [],
-                'attachment': [],
-                'tag': [{
-                    "type": "Mention",
-                    "href": ac['actor'],
-                    "name": sender.mention_username
-                }]
-            }
+            "type": "Create",
+            "actor": test_actor.url,
+            "id": "{}/activity".format(reply_url),
+            "published": now.isoformat(),
+            "to": ac["actor"],
+            "cc": [],
+            "object": {
+                "type": "Note",
+                "content": "Pong!",
+                "summary": None,
+                "published": now.isoformat(),
+                "id": reply_url,
+                "inReplyTo": ac["object"]["id"],
+                "sensitive": False,
+                "url": reply_url,
+                "to": [ac["actor"]],
+                "attributedTo": test_actor.url,
+                "cc": [],
+                "attachment": [],
+                "tag": [
+                    {
+                        "type": "Mention",
+                        "href": ac["actor"],
+                        "name": sender.mention_username,
+                    }
+                ],
+            },
         }
-        activity.deliver(
-            reply_activity,
-            to=[ac['actor']],
-            on_behalf_of=test_actor)
+        activity.deliver(reply_activity, to=[ac["actor"]], on_behalf_of=test_actor)
 
     def handle_follow(self, ac, sender):
         super().handle_follow(ac, sender)
         # also, we follow back
         test_actor = self.get_actor_instance()
         follow_back = models.Follow.objects.get_or_create(
-            actor=test_actor,
-            target=sender,
-            approved=None,
+            actor=test_actor, target=sender, approved=None
         )[0]
         activity.deliver(
             serializers.FollowSerializer(follow_back).data,
             to=[follow_back.target.url],
-            on_behalf_of=follow_back.actor)
+            on_behalf_of=follow_back.actor,
+        )
 
     def handle_undo_follow(self, ac, sender):
         super().handle_undo_follow(ac, sender)
         actor = self.get_actor_instance()
         # we also unfollow the sender, if possible
         try:
-            follow = models.Follow.objects.get(
-                target=sender,
-                actor=actor,
-            )
+            follow = models.Follow.objects.get(target=sender, actor=actor)
         except models.Follow.DoesNotExist:
             return
         undo = serializers.UndoFollowSerializer(follow).data
         follow.delete()
-        activity.deliver(
-            undo,
-            to=[sender.url],
-            on_behalf_of=actor)
+        activity.deliver(undo, to=[sender.url], on_behalf_of=actor)
 
 
-SYSTEM_ACTORS = {
-    'library': LibraryActor(),
-    'test': TestActor(),
-}
+SYSTEM_ACTORS = {"library": LibraryActor(), "test": TestActor()}
diff --git a/api/funkwhale_api/federation/admin.py b/api/funkwhale_api/federation/admin.py
index 6a097174b5..a82e9aaf24 100644
--- a/api/funkwhale_api/federation/admin.py
+++ b/api/funkwhale_api/federation/admin.py
@@ -6,61 +6,43 @@ from . import models
 @admin.register(models.Actor)
 class ActorAdmin(admin.ModelAdmin):
     list_display = [
-        'url',
-        'domain',
-        'preferred_username',
-        'type',
-        'creation_date',
-        'last_fetch_date']
-    search_fields = ['url', 'domain', 'preferred_username']
-    list_filter = [
-        'type'
+        "url",
+        "domain",
+        "preferred_username",
+        "type",
+        "creation_date",
+        "last_fetch_date",
     ]
+    search_fields = ["url", "domain", "preferred_username"]
+    list_filter = ["type"]
 
 
 @admin.register(models.Follow)
 class FollowAdmin(admin.ModelAdmin):
-    list_display = [
-        'actor',
-        'target',
-        'approved',
-        'creation_date'
-    ]
-    list_filter = [
-        'approved'
-    ]
-    search_fields = ['actor__url', 'target__url']
+    list_display = ["actor", "target", "approved", "creation_date"]
+    list_filter = ["approved"]
+    search_fields = ["actor__url", "target__url"]
     list_select_related = True
 
 
 @admin.register(models.Library)
 class LibraryAdmin(admin.ModelAdmin):
-    list_display = [
-        'actor',
-        'url',
-        'creation_date',
-        'fetched_date',
-        'tracks_count']
-    search_fields = ['actor__url', 'url']
-    list_filter = [
-        'federation_enabled',
-        'download_files',
-        'autoimport',
-    ]
+    list_display = ["actor", "url", "creation_date", "fetched_date", "tracks_count"]
+    search_fields = ["actor__url", "url"]
+    list_filter = ["federation_enabled", "download_files", "autoimport"]
     list_select_related = True
 
 
 @admin.register(models.LibraryTrack)
 class LibraryTrackAdmin(admin.ModelAdmin):
     list_display = [
-        'title',
-        'artist_name',
-        'album_title',
-        'url',
-        'library',
-        'creation_date',
-        'published_date',
+        "title",
+        "artist_name",
+        "album_title",
+        "url",
+        "library",
+        "creation_date",
+        "published_date",
     ]
-    search_fields = [
-        'library__url', 'url', 'artist_name', 'title', 'album_title']
+    search_fields = ["library__url", "url", "artist_name", "title", "album_title"]
     list_select_related = True
diff --git a/api/funkwhale_api/federation/api_urls.py b/api/funkwhale_api/federation/api_urls.py
index 41dd1c0f99..625043bf6c 100644
--- a/api/funkwhale_api/federation/api_urls.py
+++ b/api/funkwhale_api/federation/api_urls.py
@@ -3,13 +3,7 @@ from rest_framework import routers
 from . import views
 
 router = routers.SimpleRouter()
-router.register(
-    r'libraries',
-    views.LibraryViewSet,
-    'libraries')
-router.register(
-    r'library-tracks',
-    views.LibraryTrackViewSet,
-    'library-tracks')
+router.register(r"libraries", views.LibraryViewSet, "libraries")
+router.register(r"library-tracks", views.LibraryTrackViewSet, "library-tracks")
 
 urlpatterns = router.urls
diff --git a/api/funkwhale_api/federation/authentication.py b/api/funkwhale_api/federation/authentication.py
index bfd46084c0..b669b40048 100644
--- a/api/funkwhale_api/federation/authentication.py
+++ b/api/funkwhale_api/federation/authentication.py
@@ -17,7 +17,7 @@ class SignatureAuthentication(authentication.BaseAuthentication):
     def authenticate_actor(self, request):
         headers = utils.clean_wsgi_headers(request.META)
         try:
-            signature = headers['Signature']
+            signature = headers["Signature"]
             key_id = keys.get_key_id_from_signature_header(signature)
         except KeyError:
             return
@@ -25,25 +25,25 @@ class SignatureAuthentication(authentication.BaseAuthentication):
             raise exceptions.AuthenticationFailed(str(e))
 
         try:
-            actor = actors.get_actor(key_id.split('#')[0])
+            actor = actors.get_actor(key_id.split("#")[0])
         except Exception as e:
             raise exceptions.AuthenticationFailed(str(e))
 
         if not actor.public_key:
-            raise exceptions.AuthenticationFailed('No public key found')
+            raise exceptions.AuthenticationFailed("No public key found")
 
         try:
-            signing.verify_django(request, actor.public_key.encode('utf-8'))
+            signing.verify_django(request, actor.public_key.encode("utf-8"))
         except cryptography.exceptions.InvalidSignature:
-            raise exceptions.AuthenticationFailed('Invalid signature')
+            raise exceptions.AuthenticationFailed("Invalid signature")
 
         return actor
 
     def authenticate(self, request):
-        setattr(request, 'actor', None)
+        setattr(request, "actor", None)
         actor = self.authenticate_actor(request)
         if not actor:
             return
         user = AnonymousUser()
-        setattr(request, 'actor', actor)
+        setattr(request, "actor", actor)
         return (user, None)
diff --git a/api/funkwhale_api/federation/dynamic_preferences_registry.py b/api/funkwhale_api/federation/dynamic_preferences_registry.py
index 8b1b2b03f9..21f4076eac 100644
--- a/api/funkwhale_api/federation/dynamic_preferences_registry.py
+++ b/api/funkwhale_api/federation/dynamic_preferences_registry.py
@@ -4,77 +4,66 @@ from dynamic_preferences import types
 from dynamic_preferences.registries import global_preferences_registry
 
 from funkwhale_api.common import preferences
-federation = types.Section('federation')
+
+federation = types.Section("federation")
 
 
 @global_preferences_registry.register
 class MusicCacheDuration(types.IntPreference):
     show_in_api = True
     section = federation
-    name = 'music_cache_duration'
+    name = "music_cache_duration"
     default = 60 * 24 * 2
-    verbose_name = 'Music cache duration'
+    verbose_name = "Music cache duration"
     help_text = (
-        'How much minutes do you want to keep a copy of federated tracks'
-        'locally? Federated files that were not listened in this interval '
-        'will be erased and refetched from the remote on the next listening.'
+        "How much minutes do you want to keep a copy of federated tracks"
+        "locally? Federated files that were not listened in this interval "
+        "will be erased and refetched from the remote on the next listening."
     )
-    field_kwargs = {
-        'required': False,
-    }
+    field_kwargs = {"required": False}
 
 
 @global_preferences_registry.register
 class Enabled(preferences.DefaultFromSettingMixin, types.BooleanPreference):
     section = federation
-    name = 'enabled'
-    setting = 'FEDERATION_ENABLED'
-    verbose_name = 'Federation enabled'
+    name = "enabled"
+    setting = "FEDERATION_ENABLED"
+    verbose_name = "Federation enabled"
     help_text = (
-        'Use this setting to enable or disable federation logic and API'
-        ' globally.'
+        "Use this setting to enable or disable federation logic and API" " globally."
     )
 
 
 @global_preferences_registry.register
-class CollectionPageSize(
-        preferences.DefaultFromSettingMixin, types.IntPreference):
+class CollectionPageSize(preferences.DefaultFromSettingMixin, types.IntPreference):
     section = federation
-    name = 'collection_page_size'
-    setting = 'FEDERATION_COLLECTION_PAGE_SIZE'
-    verbose_name = 'Federation collection page size'
-    help_text = (
-        'How much items to display in ActivityPub collections.'
-    )
-    field_kwargs = {
-        'required': False,
-    }
+    name = "collection_page_size"
+    setting = "FEDERATION_COLLECTION_PAGE_SIZE"
+    verbose_name = "Federation collection page size"
+    help_text = "How much items to display in ActivityPub collections."
+    field_kwargs = {"required": False}
 
 
 @global_preferences_registry.register
-class ActorFetchDelay(
-        preferences.DefaultFromSettingMixin, types.IntPreference):
+class ActorFetchDelay(preferences.DefaultFromSettingMixin, types.IntPreference):
     section = federation
-    name = 'actor_fetch_delay'
-    setting = 'FEDERATION_ACTOR_FETCH_DELAY'
-    verbose_name = 'Federation actor fetch delay'
+    name = "actor_fetch_delay"
+    setting = "FEDERATION_ACTOR_FETCH_DELAY"
+    verbose_name = "Federation actor fetch delay"
     help_text = (
-        'How much minutes to wait before refetching actors on '
-        'request authentication.'
+        "How much minutes to wait before refetching actors on "
+        "request authentication."
     )
-    field_kwargs = {
-        'required': False,
-    }
+    field_kwargs = {"required": False}
 
 
 @global_preferences_registry.register
-class MusicNeedsApproval(
-        preferences.DefaultFromSettingMixin, types.BooleanPreference):
+class MusicNeedsApproval(preferences.DefaultFromSettingMixin, types.BooleanPreference):
     section = federation
-    name = 'music_needs_approval'
-    setting = 'FEDERATION_MUSIC_NEEDS_APPROVAL'
-    verbose_name = 'Federation music needs approval'
+    name = "music_needs_approval"
+    setting = "FEDERATION_MUSIC_NEEDS_APPROVAL"
+    verbose_name = "Federation music needs approval"
     help_text = (
-        'When true, other federation actors will need your approval'
-        ' before being able to browse your library.'
+        "When true, other federation actors will need your approval"
+        " before being able to browse your library."
     )
diff --git a/api/funkwhale_api/federation/exceptions.py b/api/funkwhale_api/federation/exceptions.py
index 31d864b36c..b3fb73ab8e 100644
--- a/api/funkwhale_api/federation/exceptions.py
+++ b/api/funkwhale_api/federation/exceptions.py
@@ -1,5 +1,3 @@
-
-
 class MalformedPayload(ValueError):
     pass
 
diff --git a/api/funkwhale_api/federation/factories.py b/api/funkwhale_api/federation/factories.py
index 891609cbaa..52a5e8b54d 100644
--- a/api/funkwhale_api/federation/factories.py
+++ b/api/funkwhale_api/federation/factories.py
@@ -12,29 +12,25 @@ from . import keys
 from . import models
 
 
-registry.register(keys.get_key_pair, name='federation.KeyPair')
+registry.register(keys.get_key_pair, name="federation.KeyPair")
 
 
-@registry.register(name='federation.SignatureAuth')
+@registry.register(name="federation.SignatureAuth")
 class SignatureAuthFactory(factory.Factory):
-    algorithm = 'rsa-sha256'
+    algorithm = "rsa-sha256"
     key = factory.LazyFunction(lambda: keys.get_key_pair()[0])
-    key_id = factory.Faker('url')
+    key_id = factory.Faker("url")
     use_auth_header = False
-    headers = [
-        '(request-target)',
-        'user-agent',
-        'host',
-        'date',
-        'content-type',]
+    headers = ["(request-target)", "user-agent", "host", "date", "content-type"]
+
     class Meta:
         model = requests_http_signature.HTTPSignatureAuth
 
 
-@registry.register(name='federation.SignedRequest')
+@registry.register(name="federation.SignedRequest")
 class SignedRequestFactory(factory.Factory):
-    url = factory.Faker('url')
-    method = 'get'
+    url = factory.Faker("url")
+    method = "get"
     auth = factory.SubFactory(SignatureAuthFactory)
 
     class Meta:
@@ -43,59 +39,62 @@ class SignedRequestFactory(factory.Factory):
     @factory.post_generation
     def headers(self, create, extracted, **kwargs):
         default_headers = {
-            'User-Agent': 'Test',
-            'Host': 'test.host',
-            'Date': 'Right now',
-            'Content-Type': 'application/activity+json'
+            "User-Agent": "Test",
+            "Host": "test.host",
+            "Date": "Right now",
+            "Content-Type": "application/activity+json",
         }
         if extracted:
             default_headers.update(extracted)
         self.headers.update(default_headers)
 
 
-@registry.register(name='federation.Link')
+@registry.register(name="federation.Link")
 class LinkFactory(factory.Factory):
-    type = 'Link'
-    href = factory.Faker('url')
-    mediaType = 'text/html'
+    type = "Link"
+    href = factory.Faker("url")
+    mediaType = "text/html"
 
     class Meta:
         model = dict
 
     class Params:
-        audio = factory.Trait(
-            mediaType=factory.Iterator(['audio/mp3', 'audio/ogg'])
-        )
+        audio = factory.Trait(mediaType=factory.Iterator(["audio/mp3", "audio/ogg"]))
 
 
 @registry.register
 class ActorFactory(factory.DjangoModelFactory):
     public_key = None
     private_key = None
-    preferred_username = factory.Faker('user_name')
-    summary = factory.Faker('paragraph')
-    domain = factory.Faker('domain_name')
-    url = factory.LazyAttribute(lambda o: 'https://{}/users/{}'.format(o.domain, o.preferred_username))
-    inbox_url = factory.LazyAttribute(lambda o: 'https://{}/users/{}/inbox'.format(o.domain, o.preferred_username))
-    outbox_url = factory.LazyAttribute(lambda o: 'https://{}/users/{}/outbox'.format(o.domain, o.preferred_username))
+    preferred_username = factory.Faker("user_name")
+    summary = factory.Faker("paragraph")
+    domain = factory.Faker("domain_name")
+    url = factory.LazyAttribute(
+        lambda o: "https://{}/users/{}".format(o.domain, o.preferred_username)
+    )
+    inbox_url = factory.LazyAttribute(
+        lambda o: "https://{}/users/{}/inbox".format(o.domain, o.preferred_username)
+    )
+    outbox_url = factory.LazyAttribute(
+        lambda o: "https://{}/users/{}/outbox".format(o.domain, o.preferred_username)
+    )
 
     class Meta:
         model = models.Actor
 
     class Params:
         local = factory.Trait(
-            domain=factory.LazyAttribute(
-                lambda o: settings.FEDERATION_HOSTNAME)
+            domain=factory.LazyAttribute(lambda o: settings.FEDERATION_HOSTNAME)
         )
 
     @classmethod
     def _generate(cls, create, attrs):
-        has_public = attrs.get('public_key') is not None
-        has_private = attrs.get('private_key') is not None
+        has_public = attrs.get("public_key") is not None
+        has_private = attrs.get("private_key") is not None
         if not has_public and not has_private:
             private, public = keys.get_key_pair()
-            attrs['private_key'] = private.decode('utf-8')
-            attrs['public_key'] = public.decode('utf-8')
+            attrs["private_key"] = private.decode("utf-8")
+            attrs["public_key"] = public.decode("utf-8")
         return super()._generate(create, attrs)
 
 
@@ -108,15 +107,13 @@ class FollowFactory(factory.DjangoModelFactory):
         model = models.Follow
 
     class Params:
-        local = factory.Trait(
-            actor=factory.SubFactory(ActorFactory, local=True)
-        )
+        local = factory.Trait(actor=factory.SubFactory(ActorFactory, local=True))
 
 
 @registry.register
 class LibraryFactory(factory.DjangoModelFactory):
     actor = factory.SubFactory(ActorFactory)
-    url = factory.Faker('url')
+    url = factory.Faker("url")
     federation_enabled = True
     download_files = False
     autoimport = False
@@ -126,42 +123,36 @@ class LibraryFactory(factory.DjangoModelFactory):
 
 
 class ArtistMetadataFactory(factory.Factory):
-    name = factory.Faker('name')
+    name = factory.Faker("name")
 
     class Meta:
         model = dict
 
     class Params:
-        musicbrainz = factory.Trait(
-            musicbrainz_id=factory.Faker('uuid4')
-        )
+        musicbrainz = factory.Trait(musicbrainz_id=factory.Faker("uuid4"))
 
 
 class ReleaseMetadataFactory(factory.Factory):
-    title = factory.Faker('sentence')
+    title = factory.Faker("sentence")
 
     class Meta:
         model = dict
 
     class Params:
-        musicbrainz = factory.Trait(
-            musicbrainz_id=factory.Faker('uuid4')
-        )
+        musicbrainz = factory.Trait(musicbrainz_id=factory.Faker("uuid4"))
 
 
 class RecordingMetadataFactory(factory.Factory):
-    title = factory.Faker('sentence')
+    title = factory.Faker("sentence")
 
     class Meta:
         model = dict
 
     class Params:
-        musicbrainz = factory.Trait(
-            musicbrainz_id=factory.Faker('uuid4')
-        )
+        musicbrainz = factory.Trait(musicbrainz_id=factory.Faker("uuid4"))
 
 
-@registry.register(name='federation.LibraryTrackMetadata')
+@registry.register(name="federation.LibraryTrackMetadata")
 class LibraryTrackMetadataFactory(factory.Factory):
     artist = factory.SubFactory(ArtistMetadataFactory)
     recording = factory.SubFactory(RecordingMetadataFactory)
@@ -174,64 +165,59 @@ class LibraryTrackMetadataFactory(factory.Factory):
 @registry.register
 class LibraryTrackFactory(factory.DjangoModelFactory):
     library = factory.SubFactory(LibraryFactory)
-    url = factory.Faker('url')
-    title = factory.Faker('sentence')
-    artist_name = factory.Faker('sentence')
-    album_title = factory.Faker('sentence')
-    audio_url = factory.Faker('url')
-    audio_mimetype = 'audio/ogg'
+    url = factory.Faker("url")
+    title = factory.Faker("sentence")
+    artist_name = factory.Faker("sentence")
+    album_title = factory.Faker("sentence")
+    audio_url = factory.Faker("url")
+    audio_mimetype = "audio/ogg"
     metadata = factory.SubFactory(LibraryTrackMetadataFactory)
 
     class Meta:
         model = models.LibraryTrack
 
     class Params:
-        with_audio_file = factory.Trait(
-            audio_file=factory.django.FileField()
-        )
+        with_audio_file = factory.Trait(audio_file=factory.django.FileField())
 
 
-@registry.register(name='federation.Note')
+@registry.register(name="federation.Note")
 class NoteFactory(factory.Factory):
-    type = 'Note'
-    id = factory.Faker('url')
-    published = factory.LazyFunction(
-        lambda: timezone.now().isoformat()
-    )
+    type = "Note"
+    id = factory.Faker("url")
+    published = factory.LazyFunction(lambda: timezone.now().isoformat())
     inReplyTo = None
-    content = factory.Faker('sentence')
+    content = factory.Faker("sentence")
 
     class Meta:
         model = dict
 
 
-@registry.register(name='federation.Activity')
+@registry.register(name="federation.Activity")
 class ActivityFactory(factory.Factory):
-    type = 'Create'
-    id = factory.Faker('url')
-    published = factory.LazyFunction(
-        lambda: timezone.now().isoformat()
-    )
-    actor = factory.Faker('url')
+    type = "Create"
+    id = factory.Faker("url")
+    published = factory.LazyFunction(lambda: timezone.now().isoformat())
+    actor = factory.Faker("url")
     object = factory.SubFactory(
         NoteFactory,
-        actor=factory.SelfAttribute('..actor'),
-        published=factory.SelfAttribute('..published'))
+        actor=factory.SelfAttribute("..actor"),
+        published=factory.SelfAttribute("..published"),
+    )
 
     class Meta:
         model = dict
 
 
-@registry.register(name='federation.AudioMetadata')
+@registry.register(name="federation.AudioMetadata")
 class AudioMetadataFactory(factory.Factory):
     recording = factory.LazyAttribute(
-        lambda o: 'https://musicbrainz.org/recording/{}'.format(uuid.uuid4())
+        lambda o: "https://musicbrainz.org/recording/{}".format(uuid.uuid4())
     )
     artist = factory.LazyAttribute(
-        lambda o: 'https://musicbrainz.org/artist/{}'.format(uuid.uuid4())
+        lambda o: "https://musicbrainz.org/artist/{}".format(uuid.uuid4())
     )
     release = factory.LazyAttribute(
-        lambda o: 'https://musicbrainz.org/release/{}'.format(uuid.uuid4())
+        lambda o: "https://musicbrainz.org/release/{}".format(uuid.uuid4())
     )
     bitrate = 42
     length = 43
@@ -241,14 +227,12 @@ class AudioMetadataFactory(factory.Factory):
         model = dict
 
 
-@registry.register(name='federation.Audio')
+@registry.register(name="federation.Audio")
 class AudioFactory(factory.Factory):
-    type = 'Audio'
-    id = factory.Faker('url')
-    published = factory.LazyFunction(
-        lambda: timezone.now().isoformat()
-    )
-    actor = factory.Faker('url')
+    type = "Audio"
+    id = factory.Faker("url")
+    published = factory.LazyFunction(lambda: timezone.now().isoformat())
+    actor = factory.Faker("url")
     url = factory.SubFactory(LinkFactory, audio=True)
     metadata = factory.SubFactory(LibraryTrackMetadataFactory)
 
diff --git a/api/funkwhale_api/federation/filters.py b/api/funkwhale_api/federation/filters.py
index 1d93f68b99..3b5bfd7395 100644
--- a/api/funkwhale_api/federation/filters.py
+++ b/api/funkwhale_api/federation/filters.py
@@ -6,73 +6,67 @@ from . import models
 
 
 class LibraryFilter(django_filters.FilterSet):
-    approved = django_filters.BooleanFilter('following__approved')
-    q = fields.SearchFilter(search_fields=[
-        'actor__domain',
-    ])
+    approved = django_filters.BooleanFilter("following__approved")
+    q = fields.SearchFilter(search_fields=["actor__domain"])
 
     class Meta:
         model = models.Library
         fields = {
-            'approved': ['exact'],
-            'federation_enabled': ['exact'],
-            'download_files': ['exact'],
-            'autoimport': ['exact'],
-            'tracks_count': ['exact'],
+            "approved": ["exact"],
+            "federation_enabled": ["exact"],
+            "download_files": ["exact"],
+            "autoimport": ["exact"],
+            "tracks_count": ["exact"],
         }
 
 
 class LibraryTrackFilter(django_filters.FilterSet):
-    library = django_filters.CharFilter('library__uuid')
-    status = django_filters.CharFilter(method='filter_status')
-    q = fields.SearchFilter(search_fields=[
-        'artist_name',
-        'title',
-        'album_title',
-        'library__actor__domain',
-    ])
+    library = django_filters.CharFilter("library__uuid")
+    status = django_filters.CharFilter(method="filter_status")
+    q = fields.SearchFilter(
+        search_fields=["artist_name", "title", "album_title", "library__actor__domain"]
+    )
 
     def filter_status(self, queryset, field_name, value):
-        if value == 'imported':
+        if value == "imported":
             return queryset.filter(local_track_file__isnull=False)
-        elif value == 'not_imported':
-            return queryset.filter(
-                local_track_file__isnull=True
-            ).exclude(import_jobs__status='pending')
-        elif value == 'import_pending':
-            return queryset.filter(import_jobs__status='pending')
+        elif value == "not_imported":
+            return queryset.filter(local_track_file__isnull=True).exclude(
+                import_jobs__status="pending"
+            )
+        elif value == "import_pending":
+            return queryset.filter(import_jobs__status="pending")
         return queryset
 
     class Meta:
         model = models.LibraryTrack
         fields = {
-            'library': ['exact'],
-            'artist_name': ['exact', 'icontains'],
-            'title': ['exact', 'icontains'],
-            'album_title': ['exact', 'icontains'],
-            'audio_mimetype': ['exact', 'icontains'],
+            "library": ["exact"],
+            "artist_name": ["exact", "icontains"],
+            "title": ["exact", "icontains"],
+            "album_title": ["exact", "icontains"],
+            "audio_mimetype": ["exact", "icontains"],
         }
 
 
 class FollowFilter(django_filters.FilterSet):
-    pending = django_filters.CharFilter(method='filter_pending')
+    pending = django_filters.CharFilter(method="filter_pending")
     ordering = django_filters.OrderingFilter(
         # tuple-mapping retains order
         fields=(
-            ('creation_date', 'creation_date'),
-            ('modification_date', 'modification_date'),
-        ),
+            ("creation_date", "creation_date"),
+            ("modification_date", "modification_date"),
+        )
+    )
+    q = fields.SearchFilter(
+        search_fields=["actor__domain", "actor__preferred_username"]
     )
-    q = fields.SearchFilter(search_fields=[
-        'actor__domain',
-        'actor__preferred_username',
-    ])
 
     class Meta:
         model = models.Follow
-        fields = ['approved', 'pending', 'q']
+        fields = ["approved", "pending", "q"]
 
     def filter_pending(self, queryset, field_name, value):
-        if value.lower() in ['true', '1', 'yes']:
+        if value.lower() in ["true", "1", "yes"]:
             queryset = queryset.filter(approved__isnull=True)
         return queryset
diff --git a/api/funkwhale_api/federation/keys.py b/api/funkwhale_api/federation/keys.py
index 7e9d316c25..fb6f0748c9 100644
--- a/api/funkwhale_api/federation/keys.py
+++ b/api/funkwhale_api/federation/keys.py
@@ -7,42 +7,40 @@ import urllib.parse
 
 from . import exceptions
 
-KEY_ID_REGEX = re.compile(r'keyId=\"(?P<id>.*)\"')
+KEY_ID_REGEX = re.compile(r"keyId=\"(?P<id>.*)\"")
 
 
 def get_key_pair(size=2048):
     key = rsa.generate_private_key(
-        backend=crypto_default_backend(),
-        public_exponent=65537,
-        key_size=size
+        backend=crypto_default_backend(), public_exponent=65537, key_size=size
     )
     private_key = key.private_bytes(
         crypto_serialization.Encoding.PEM,
         crypto_serialization.PrivateFormat.PKCS8,
-        crypto_serialization.NoEncryption())
+        crypto_serialization.NoEncryption(),
+    )
     public_key = key.public_key().public_bytes(
-        crypto_serialization.Encoding.PEM,
-        crypto_serialization.PublicFormat.PKCS1
+        crypto_serialization.Encoding.PEM, crypto_serialization.PublicFormat.PKCS1
     )
 
     return private_key, public_key
 
 
 def get_key_id_from_signature_header(header_string):
-    parts = header_string.split(',')
+    parts = header_string.split(",")
     try:
         raw_key_id = [p for p in parts if p.startswith('keyId="')][0]
     except IndexError:
-        raise ValueError('Missing key id')
+        raise ValueError("Missing key id")
 
     match = KEY_ID_REGEX.match(raw_key_id)
     if not match:
-        raise ValueError('Invalid key id')
+        raise ValueError("Invalid key id")
 
     key_id = match.groups()[0]
     url = urllib.parse.urlparse(key_id)
     if not url.scheme or not url.netloc:
-        raise ValueError('Invalid url')
-    if url.scheme not in ['http', 'https']:
-        raise ValueError('Invalid shceme')
+        raise ValueError("Invalid url")
+    if url.scheme not in ["http", "https"]:
+        raise ValueError("Invalid shceme")
     return key_id
diff --git a/api/funkwhale_api/federation/library.py b/api/funkwhale_api/federation/library.py
index c53ce54306..28bb9a762a 100644
--- a/api/funkwhale_api/federation/library.py
+++ b/api/funkwhale_api/federation/library.py
@@ -24,87 +24,66 @@ def scan_from_account_name(account_name):
     """
     data = {}
     try:
-        username, domain = webfinger.clean_acct(
-            account_name, ensure_local=False)
+        username, domain = webfinger.clean_acct(account_name, ensure_local=False)
     except serializers.ValidationError:
-        return {
-            'webfinger': {
-                'errors': ['Invalid account string']
-            }
-        }
-    system_library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    library = models.Library.objects.filter(
-        actor__domain=domain,
-        actor__preferred_username=username
-    ).select_related('actor').first()
-    data['local'] = {
-        'following': False,
-        'awaiting_approval': False,
-    }
+        return {"webfinger": {"errors": ["Invalid account string"]}}
+    system_library = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    library = (
+        models.Library.objects.filter(
+            actor__domain=domain, actor__preferred_username=username
+        )
+        .select_related("actor")
+        .first()
+    )
+    data["local"] = {"following": False, "awaiting_approval": False}
     try:
         follow = models.Follow.objects.get(
             target__preferred_username=username,
             target__domain=username,
             actor=system_library,
         )
-        data['local']['awaiting_approval'] = not bool(follow.approved)
-        data['local']['following'] = True
+        data["local"]["awaiting_approval"] = not bool(follow.approved)
+        data["local"]["following"] = True
     except models.Follow.DoesNotExist:
         pass
 
     try:
-        data['webfinger'] = webfinger.get_resource(
-            'acct:{}'.format(account_name))
+        data["webfinger"] = webfinger.get_resource("acct:{}".format(account_name))
     except requests.ConnectionError:
-        return {
-            'webfinger': {
-                'errors': ['This webfinger resource is not reachable']
-            }
-        }
+        return {"webfinger": {"errors": ["This webfinger resource is not reachable"]}}
     except requests.HTTPError as e:
         return {
-            'webfinger': {
-                'errors': [
-                    'Error {} during webfinger request'.format(
-                        e.response.status_code)]
+            "webfinger": {
+                "errors": [
+                    "Error {} during webfinger request".format(e.response.status_code)
+                ]
             }
         }
     except json.JSONDecodeError as e:
-        return {
-            'webfinger': {
-                'errors': ['Could not process webfinger response']
-            }
-        }
+        return {"webfinger": {"errors": ["Could not process webfinger response"]}}
 
     try:
-        data['actor'] = actors.get_actor_data(data['webfinger']['actor_url'])
+        data["actor"] = actors.get_actor_data(data["webfinger"]["actor_url"])
     except requests.ConnectionError:
-        data['actor'] = {
-            'errors': ['This actor is not reachable']
-        }
+        data["actor"] = {"errors": ["This actor is not reachable"]}
         return data
     except requests.HTTPError as e:
-        data['actor'] = {
-            'errors': [
-                'Error {} during actor request'.format(
-                    e.response.status_code)]
+        data["actor"] = {
+            "errors": ["Error {} during actor request".format(e.response.status_code)]
         }
         return data
 
-    serializer = serializers.LibraryActorSerializer(data=data['actor'])
+    serializer = serializers.LibraryActorSerializer(data=data["actor"])
     if not serializer.is_valid():
-        data['actor'] = {
-            'errors': ['Invalid ActivityPub actor']
-        }
+        data["actor"] = {"errors": ["Invalid ActivityPub actor"]}
         return data
-    data['library'] = get_library_data(
-        serializer.validated_data['library_url'])
+    data["library"] = get_library_data(serializer.validated_data["library_url"])
 
     return data
 
 
 def get_library_data(library_url):
-    actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
+    actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
     auth = signing.get_auth(actor.private_key, actor.private_key_id)
     try:
         response = session.get_session().get(
@@ -112,55 +91,37 @@ def get_library_data(library_url):
             auth=auth,
             timeout=5,
             verify=settings.EXTERNAL_REQUESTS_VERIFY_SSL,
-            headers={
-                'Content-Type': 'application/activity+json'
-            }
+            headers={"Content-Type": "application/activity+json"},
         )
     except requests.ConnectionError:
-        return {
-            'errors': ['This library is not reachable']
-        }
+        return {"errors": ["This library is not reachable"]}
     scode = response.status_code
     if scode == 401:
-        return {
-            'errors': ['This library requires authentication']
-        }
+        return {"errors": ["This library requires authentication"]}
     elif scode == 403:
-        return {
-            'errors': ['Permission denied while scanning library']
-        }
+        return {"errors": ["Permission denied while scanning library"]}
     elif scode >= 400:
-        return {
-            'errors': ['Error {} while fetching the library'.format(scode)]
-        }
-    serializer = serializers.PaginatedCollectionSerializer(
-        data=response.json(),
-    )
+        return {"errors": ["Error {} while fetching the library".format(scode)]}
+    serializer = serializers.PaginatedCollectionSerializer(data=response.json())
     if not serializer.is_valid():
-        return {
-            'errors': [
-                'Invalid ActivityPub response from remote library']
-        }
+        return {"errors": ["Invalid ActivityPub response from remote library"]}
 
     return serializer.validated_data
 
 
 def get_library_page(library, page_url):
-    actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
+    actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
     auth = signing.get_auth(actor.private_key, actor.private_key_id)
     response = session.get_session().get(
         page_url,
         auth=auth,
         timeout=5,
         verify=settings.EXTERNAL_REQUESTS_VERIFY_SSL,
-        headers={
-            'Content-Type': 'application/activity+json'
-        }
+        headers={"Content-Type": "application/activity+json"},
     )
     serializer = serializers.CollectionPageSerializer(
         data=response.json(),
-        context={
-            'library': library,
-            'item_serializer': serializers.AudioSerializer})
+        context={"library": library, "item_serializer": serializers.AudioSerializer},
+    )
     serializer.is_valid(raise_exception=True)
     return serializer.validated_data
diff --git a/api/funkwhale_api/federation/migrations/0001_initial.py b/api/funkwhale_api/federation/migrations/0001_initial.py
index a9157e57e3..a4c641b4e8 100644
--- a/api/funkwhale_api/federation/migrations/0001_initial.py
+++ b/api/funkwhale_api/federation/migrations/0001_initial.py
@@ -8,30 +8,74 @@ class Migration(migrations.Migration):
 
     initial = True
 
-    dependencies = [
-    ]
+    dependencies = []
 
     operations = [
         migrations.CreateModel(
-            name='Actor',
+            name="Actor",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('url', models.URLField(db_index=True, max_length=500, unique=True)),
-                ('outbox_url', models.URLField(max_length=500)),
-                ('inbox_url', models.URLField(max_length=500)),
-                ('following_url', models.URLField(blank=True, max_length=500, null=True)),
-                ('followers_url', models.URLField(blank=True, max_length=500, null=True)),
-                ('shared_inbox_url', models.URLField(blank=True, max_length=500, null=True)),
-                ('type', models.CharField(choices=[('Person', 'Person'), ('Application', 'Application'), ('Group', 'Group'), ('Organization', 'Organization'), ('Service', 'Service')], default='Person', max_length=25)),
-                ('name', models.CharField(blank=True, max_length=200, null=True)),
-                ('domain', models.CharField(max_length=1000)),
-                ('summary', models.CharField(blank=True, max_length=500, null=True)),
-                ('preferred_username', models.CharField(blank=True, max_length=200, null=True)),
-                ('public_key', models.CharField(blank=True, max_length=5000, null=True)),
-                ('private_key', models.CharField(blank=True, max_length=5000, null=True)),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('last_fetch_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('manually_approves_followers', models.NullBooleanField(default=None)),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("url", models.URLField(db_index=True, max_length=500, unique=True)),
+                ("outbox_url", models.URLField(max_length=500)),
+                ("inbox_url", models.URLField(max_length=500)),
+                (
+                    "following_url",
+                    models.URLField(blank=True, max_length=500, null=True),
+                ),
+                (
+                    "followers_url",
+                    models.URLField(blank=True, max_length=500, null=True),
+                ),
+                (
+                    "shared_inbox_url",
+                    models.URLField(blank=True, max_length=500, null=True),
+                ),
+                (
+                    "type",
+                    models.CharField(
+                        choices=[
+                            ("Person", "Person"),
+                            ("Application", "Application"),
+                            ("Group", "Group"),
+                            ("Organization", "Organization"),
+                            ("Service", "Service"),
+                        ],
+                        default="Person",
+                        max_length=25,
+                    ),
+                ),
+                ("name", models.CharField(blank=True, max_length=200, null=True)),
+                ("domain", models.CharField(max_length=1000)),
+                ("summary", models.CharField(blank=True, max_length=500, null=True)),
+                (
+                    "preferred_username",
+                    models.CharField(blank=True, max_length=200, null=True),
+                ),
+                (
+                    "public_key",
+                    models.CharField(blank=True, max_length=5000, null=True),
+                ),
+                (
+                    "private_key",
+                    models.CharField(blank=True, max_length=5000, null=True),
+                ),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                (
+                    "last_fetch_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                ("manually_approves_followers", models.NullBooleanField(default=None)),
             ],
-        ),
+        )
     ]
diff --git a/api/funkwhale_api/federation/migrations/0002_auto_20180403_1620.py b/api/funkwhale_api/federation/migrations/0002_auto_20180403_1620.py
index 2200424d8e..9c848ac586 100644
--- a/api/funkwhale_api/federation/migrations/0002_auto_20180403_1620.py
+++ b/api/funkwhale_api/federation/migrations/0002_auto_20180403_1620.py
@@ -5,13 +5,10 @@ from django.db import migrations
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('federation', '0001_initial'),
-    ]
+    dependencies = [("federation", "0001_initial")]
 
     operations = [
         migrations.AlterUniqueTogether(
-            name='actor',
-            unique_together={('domain', 'preferred_username')},
-        ),
+            name="actor", unique_together={("domain", "preferred_username")}
+        )
     ]
diff --git a/api/funkwhale_api/federation/migrations/0003_auto_20180407_1010.py b/api/funkwhale_api/federation/migrations/0003_auto_20180407_1010.py
index 12e3d73fed..021b2ad1c8 100644
--- a/api/funkwhale_api/federation/migrations/0003_auto_20180407_1010.py
+++ b/api/funkwhale_api/federation/migrations/0003_auto_20180407_1010.py
@@ -10,7 +10,7 @@ import uuid
 def delete_system_actors(apps, schema_editor):
     """Revert site domain and name to default."""
     Actor = apps.get_model("federation", "Actor")
-    Actor.objects.filter(preferred_username__in=['test', 'library']).delete()
+    Actor.objects.filter(preferred_username__in=["test", "library"]).delete()
 
 
 def backward(apps, schema_editor):
@@ -19,76 +19,168 @@ def backward(apps, schema_editor):
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('federation', '0002_auto_20180403_1620'),
-    ]
+    dependencies = [("federation", "0002_auto_20180403_1620")]
 
     operations = [
         migrations.RunPython(delete_system_actors, backward),
         migrations.CreateModel(
-            name='Follow',
+            name="Follow",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('uuid', models.UUIDField(default=uuid.uuid4, unique=True)),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('modification_date', models.DateTimeField(auto_now=True)),
-                ('actor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='emitted_follows', to='federation.Actor')),
-                ('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_follows', to='federation.Actor')),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("uuid", models.UUIDField(default=uuid.uuid4, unique=True)),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                ("modification_date", models.DateTimeField(auto_now=True)),
+                (
+                    "actor",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="emitted_follows",
+                        to="federation.Actor",
+                    ),
+                ),
+                (
+                    "target",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="received_follows",
+                        to="federation.Actor",
+                    ),
+                ),
             ],
         ),
         migrations.CreateModel(
-            name='FollowRequest',
+            name="FollowRequest",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('uuid', models.UUIDField(default=uuid.uuid4, unique=True)),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('modification_date', models.DateTimeField(auto_now=True)),
-                ('approved', models.NullBooleanField(default=None)),
-                ('actor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='emmited_follow_requests', to='federation.Actor')),
-                ('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_follow_requests', to='federation.Actor')),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("uuid", models.UUIDField(default=uuid.uuid4, unique=True)),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                ("modification_date", models.DateTimeField(auto_now=True)),
+                ("approved", models.NullBooleanField(default=None)),
+                (
+                    "actor",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="emmited_follow_requests",
+                        to="federation.Actor",
+                    ),
+                ),
+                (
+                    "target",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="received_follow_requests",
+                        to="federation.Actor",
+                    ),
+                ),
             ],
         ),
         migrations.CreateModel(
-            name='Library',
+            name="Library",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('modification_date', models.DateTimeField(auto_now=True)),
-                ('fetched_date', models.DateTimeField(blank=True, null=True)),
-                ('uuid', models.UUIDField(default=uuid.uuid4)),
-                ('url', models.URLField()),
-                ('federation_enabled', models.BooleanField()),
-                ('download_files', models.BooleanField()),
-                ('autoimport', models.BooleanField()),
-                ('tracks_count', models.PositiveIntegerField(blank=True, null=True)),
-                ('actor', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='library', to='federation.Actor')),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                ("modification_date", models.DateTimeField(auto_now=True)),
+                ("fetched_date", models.DateTimeField(blank=True, null=True)),
+                ("uuid", models.UUIDField(default=uuid.uuid4)),
+                ("url", models.URLField()),
+                ("federation_enabled", models.BooleanField()),
+                ("download_files", models.BooleanField()),
+                ("autoimport", models.BooleanField()),
+                ("tracks_count", models.PositiveIntegerField(blank=True, null=True)),
+                (
+                    "actor",
+                    models.OneToOneField(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="library",
+                        to="federation.Actor",
+                    ),
+                ),
             ],
         ),
         migrations.CreateModel(
-            name='LibraryTrack',
+            name="LibraryTrack",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('url', models.URLField(unique=True)),
-                ('audio_url', models.URLField()),
-                ('audio_mimetype', models.CharField(max_length=200)),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('modification_date', models.DateTimeField(auto_now=True)),
-                ('fetched_date', models.DateTimeField(blank=True, null=True)),
-                ('published_date', models.DateTimeField(blank=True, null=True)),
-                ('artist_name', models.CharField(max_length=500)),
-                ('album_title', models.CharField(max_length=500)),
-                ('title', models.CharField(max_length=500)),
-                ('metadata', django.contrib.postgres.fields.jsonb.JSONField(default={}, max_length=10000)),
-                ('library', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tracks', to='federation.Library')),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("url", models.URLField(unique=True)),
+                ("audio_url", models.URLField()),
+                ("audio_mimetype", models.CharField(max_length=200)),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                ("modification_date", models.DateTimeField(auto_now=True)),
+                ("fetched_date", models.DateTimeField(blank=True, null=True)),
+                ("published_date", models.DateTimeField(blank=True, null=True)),
+                ("artist_name", models.CharField(max_length=500)),
+                ("album_title", models.CharField(max_length=500)),
+                ("title", models.CharField(max_length=500)),
+                (
+                    "metadata",
+                    django.contrib.postgres.fields.jsonb.JSONField(
+                        default={}, max_length=10000
+                    ),
+                ),
+                (
+                    "library",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="tracks",
+                        to="federation.Library",
+                    ),
+                ),
             ],
         ),
         migrations.AddField(
-            model_name='actor',
-            name='followers',
-            field=models.ManyToManyField(related_name='following', through='federation.Follow', to='federation.Actor'),
+            model_name="actor",
+            name="followers",
+            field=models.ManyToManyField(
+                related_name="following",
+                through="federation.Follow",
+                to="federation.Actor",
+            ),
         ),
         migrations.AlterUniqueTogether(
-            name='follow',
-            unique_together={('actor', 'target')},
+            name="follow", unique_together={("actor", "target")}
         ),
     ]
diff --git a/api/funkwhale_api/federation/migrations/0004_auto_20180410_2025.py b/api/funkwhale_api/federation/migrations/0004_auto_20180410_2025.py
index bea4d14ae6..f0e5cf1d6a 100644
--- a/api/funkwhale_api/federation/migrations/0004_auto_20180410_2025.py
+++ b/api/funkwhale_api/federation/migrations/0004_auto_20180410_2025.py
@@ -6,30 +6,26 @@ import django.db.models.deletion
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('federation', '0003_auto_20180407_1010'),
-    ]
+    dependencies = [("federation", "0003_auto_20180407_1010")]
 
     operations = [
-        migrations.RemoveField(
-            model_name='followrequest',
-            name='actor',
-        ),
-        migrations.RemoveField(
-            model_name='followrequest',
-            name='target',
-        ),
+        migrations.RemoveField(model_name="followrequest", name="actor"),
+        migrations.RemoveField(model_name="followrequest", name="target"),
         migrations.AddField(
-            model_name='follow',
-            name='approved',
+            model_name="follow",
+            name="approved",
             field=models.NullBooleanField(default=None),
         ),
         migrations.AddField(
-            model_name='library',
-            name='follow',
-            field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='library', to='federation.Follow'),
-        ),
-        migrations.DeleteModel(
-            name='FollowRequest',
+            model_name="library",
+            name="follow",
+            field=models.OneToOneField(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.SET_NULL,
+                related_name="library",
+                to="federation.Follow",
+            ),
         ),
+        migrations.DeleteModel(name="FollowRequest"),
     ]
diff --git a/api/funkwhale_api/federation/migrations/0005_auto_20180413_1723.py b/api/funkwhale_api/federation/migrations/0005_auto_20180413_1723.py
index 00ba5c83de..0b2029e95c 100644
--- a/api/funkwhale_api/federation/migrations/0005_auto_20180413_1723.py
+++ b/api/funkwhale_api/federation/migrations/0005_auto_20180413_1723.py
@@ -8,19 +8,25 @@ import funkwhale_api.federation.models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('federation', '0004_auto_20180410_2025'),
-    ]
+    dependencies = [("federation", "0004_auto_20180410_2025")]
 
     operations = [
         migrations.AddField(
-            model_name='librarytrack',
-            name='audio_file',
-            field=models.FileField(blank=True, null=True, upload_to=funkwhale_api.federation.models.get_file_path),
+            model_name="librarytrack",
+            name="audio_file",
+            field=models.FileField(
+                blank=True,
+                null=True,
+                upload_to=funkwhale_api.federation.models.get_file_path,
+            ),
         ),
         migrations.AlterField(
-            model_name='librarytrack',
-            name='metadata',
-            field=django.contrib.postgres.fields.jsonb.JSONField(default={}, encoder=django.core.serializers.json.DjangoJSONEncoder, max_length=10000),
+            model_name="librarytrack",
+            name="metadata",
+            field=django.contrib.postgres.fields.jsonb.JSONField(
+                default={},
+                encoder=django.core.serializers.json.DjangoJSONEncoder,
+                max_length=10000,
+            ),
         ),
     ]
diff --git a/api/funkwhale_api/federation/migrations/0006_auto_20180521_1702.py b/api/funkwhale_api/federation/migrations/0006_auto_20180521_1702.py
index 7dcf85670b..eb731f0aa6 100644
--- a/api/funkwhale_api/federation/migrations/0006_auto_20180521_1702.py
+++ b/api/funkwhale_api/federation/migrations/0006_auto_20180521_1702.py
@@ -5,24 +5,20 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('federation', '0005_auto_20180413_1723'),
-    ]
+    dependencies = [("federation", "0005_auto_20180413_1723")]
 
     operations = [
         migrations.AlterField(
-            model_name='library',
-            name='url',
-            field=models.URLField(max_length=500),
+            model_name="library", name="url", field=models.URLField(max_length=500)
         ),
         migrations.AlterField(
-            model_name='librarytrack',
-            name='audio_url',
+            model_name="librarytrack",
+            name="audio_url",
             field=models.URLField(max_length=500),
         ),
         migrations.AlterField(
-            model_name='librarytrack',
-            name='url',
+            model_name="librarytrack",
+            name="url",
             field=models.URLField(max_length=500, unique=True),
         ),
     ]
diff --git a/api/funkwhale_api/federation/models.py b/api/funkwhale_api/federation/models.py
index 8b4f284756..398ab23c4d 100644
--- a/api/funkwhale_api/federation/models.py
+++ b/api/funkwhale_api/federation/models.py
@@ -12,16 +12,16 @@ from funkwhale_api.common import session
 from funkwhale_api.music import utils as music_utils
 
 TYPE_CHOICES = [
-    ('Person', 'Person'),
-    ('Application', 'Application'),
-    ('Group', 'Group'),
-    ('Organization', 'Organization'),
-    ('Service', 'Service'),
+    ("Person", "Person"),
+    ("Application", "Application"),
+    ("Group", "Group"),
+    ("Organization", "Organization"),
+    ("Service", "Service"),
 ]
 
 
 class Actor(models.Model):
-    ap_type = 'Actor'
+    ap_type = "Actor"
 
     url = models.URLField(unique=True, max_length=500, db_index=True)
     outbox_url = models.URLField(max_length=500)
@@ -29,49 +29,41 @@ class Actor(models.Model):
     following_url = models.URLField(max_length=500, null=True, blank=True)
     followers_url = models.URLField(max_length=500, null=True, blank=True)
     shared_inbox_url = models.URLField(max_length=500, null=True, blank=True)
-    type = models.CharField(
-        choices=TYPE_CHOICES, default='Person', max_length=25)
+    type = models.CharField(choices=TYPE_CHOICES, default="Person", max_length=25)
     name = models.CharField(max_length=200, null=True, blank=True)
     domain = models.CharField(max_length=1000)
     summary = models.CharField(max_length=500, null=True, blank=True)
-    preferred_username = models.CharField(
-        max_length=200, null=True, blank=True)
+    preferred_username = models.CharField(max_length=200, null=True, blank=True)
     public_key = models.CharField(max_length=5000, null=True, blank=True)
     private_key = models.CharField(max_length=5000, null=True, blank=True)
     creation_date = models.DateTimeField(default=timezone.now)
-    last_fetch_date = models.DateTimeField(
-        default=timezone.now)
+    last_fetch_date = models.DateTimeField(default=timezone.now)
     manually_approves_followers = models.NullBooleanField(default=None)
     followers = models.ManyToManyField(
-        to='self',
+        to="self",
         symmetrical=False,
-        through='Follow',
-        through_fields=('target', 'actor'),
-        related_name='following',
+        through="Follow",
+        through_fields=("target", "actor"),
+        related_name="following",
     )
 
     class Meta:
-        unique_together = ['domain', 'preferred_username']
+        unique_together = ["domain", "preferred_username"]
 
     @property
     def webfinger_subject(self):
-        return '{}@{}'.format(
-            self.preferred_username,
-            settings.FEDERATION_HOSTNAME,
-        )
+        return "{}@{}".format(self.preferred_username, settings.FEDERATION_HOSTNAME)
 
     @property
     def private_key_id(self):
-        return '{}#main-key'.format(self.url)
+        return "{}#main-key".format(self.url)
 
     @property
     def mention_username(self):
-        return '@{}@{}'.format(self.preferred_username, self.domain)
+        return "@{}@{}".format(self.preferred_username, self.domain)
 
     def save(self, **kwargs):
-        lowercase_fields = [
-            'domain',
-        ]
+        lowercase_fields = ["domain"]
         for field in lowercase_fields:
             v = getattr(self, field, None)
             if v:
@@ -86,58 +78,54 @@ class Actor(models.Model):
     @property
     def is_system(self):
         from . import actors
-        return all([
-            settings.FEDERATION_HOSTNAME == self.domain,
-            self.preferred_username in actors.SYSTEM_ACTORS
-        ])
+
+        return all(
+            [
+                settings.FEDERATION_HOSTNAME == self.domain,
+                self.preferred_username in actors.SYSTEM_ACTORS,
+            ]
+        )
 
     @property
     def system_conf(self):
         from . import actors
+
         if self.is_system:
             return actors.SYSTEM_ACTORS[self.preferred_username]
 
     def get_approved_followers(self):
         follows = self.received_follows.filter(approved=True)
-        return self.followers.filter(
-            pk__in=follows.values_list('actor', flat=True))
+        return self.followers.filter(pk__in=follows.values_list("actor", flat=True))
 
 
 class Follow(models.Model):
-    ap_type = 'Follow'
+    ap_type = "Follow"
 
     uuid = models.UUIDField(default=uuid.uuid4, unique=True)
     actor = models.ForeignKey(
-        Actor,
-        related_name='emitted_follows',
-        on_delete=models.CASCADE,
+        Actor, related_name="emitted_follows", on_delete=models.CASCADE
     )
     target = models.ForeignKey(
-        Actor,
-        related_name='received_follows',
-        on_delete=models.CASCADE,
+        Actor, related_name="received_follows", on_delete=models.CASCADE
     )
     creation_date = models.DateTimeField(default=timezone.now)
-    modification_date = models.DateTimeField(
-        auto_now=True)
+    modification_date = models.DateTimeField(auto_now=True)
     approved = models.NullBooleanField(default=None)
 
     class Meta:
-        unique_together = ['actor', 'target']
+        unique_together = ["actor", "target"]
 
     def get_federation_url(self):
-        return '{}#follows/{}'.format(self.actor.url, self.uuid)
+        return "{}#follows/{}".format(self.actor.url, self.uuid)
 
 
 class Library(models.Model):
     creation_date = models.DateTimeField(default=timezone.now)
-    modification_date = models.DateTimeField(
-        auto_now=True)
+    modification_date = models.DateTimeField(auto_now=True)
     fetched_date = models.DateTimeField(null=True, blank=True)
     actor = models.OneToOneField(
-        Actor,
-        on_delete=models.CASCADE,
-        related_name='library')
+        Actor, on_delete=models.CASCADE, related_name="library"
+    )
     uuid = models.UUIDField(default=uuid.uuid4)
     url = models.URLField(max_length=500)
 
@@ -149,69 +137,60 @@ class Library(models.Model):
     autoimport = models.BooleanField()
     tracks_count = models.PositiveIntegerField(null=True, blank=True)
     follow = models.OneToOneField(
-        Follow,
-        related_name='library',
-        null=True,
-        blank=True,
-        on_delete=models.SET_NULL,
+        Follow, related_name="library", null=True, blank=True, on_delete=models.SET_NULL
     )
 
 
 def get_file_path(instance, filename):
     uid = str(uuid.uuid4())
     chunk_size = 2
-    chunks = [uid[i:i+chunk_size] for i in range(0, len(uid), chunk_size)]
+    chunks = [uid[i : i + chunk_size] for i in range(0, len(uid), chunk_size)]
     parts = chunks[:3] + [filename]
-    return os.path.join('federation_cache', *parts)
+    return os.path.join("federation_cache", *parts)
 
 
 class LibraryTrack(models.Model):
     url = models.URLField(unique=True, max_length=500)
     audio_url = models.URLField(max_length=500)
     audio_mimetype = models.CharField(max_length=200)
-    audio_file = models.FileField(
-        upload_to=get_file_path,
-        null=True,
-        blank=True)
+    audio_file = models.FileField(upload_to=get_file_path, null=True, blank=True)
 
     creation_date = models.DateTimeField(default=timezone.now)
-    modification_date = models.DateTimeField(
-        auto_now=True)
+    modification_date = models.DateTimeField(auto_now=True)
     fetched_date = models.DateTimeField(null=True, blank=True)
     published_date = models.DateTimeField(null=True, blank=True)
     library = models.ForeignKey(
-        Library, related_name='tracks', on_delete=models.CASCADE)
+        Library, related_name="tracks", on_delete=models.CASCADE
+    )
     artist_name = models.CharField(max_length=500)
     album_title = models.CharField(max_length=500)
     title = models.CharField(max_length=500)
-    metadata = JSONField(
-        default={}, max_length=10000, encoder=DjangoJSONEncoder)
+    metadata = JSONField(default={}, max_length=10000, encoder=DjangoJSONEncoder)
 
     @property
     def mbid(self):
         try:
-            return self.metadata['recording']['musicbrainz_id']
+            return self.metadata["recording"]["musicbrainz_id"]
         except KeyError:
             pass
 
     def download_audio(self):
         from . import actors
-        auth = actors.SYSTEM_ACTORS['library'].get_request_auth()
+
+        auth = actors.SYSTEM_ACTORS["library"].get_request_auth()
         remote_response = session.get_session().get(
             self.audio_url,
             auth=auth,
             stream=True,
             timeout=20,
             verify=settings.EXTERNAL_REQUESTS_VERIFY_SSL,
-            headers={
-                'Content-Type': 'application/activity+json'
-            }
+            headers={"Content-Type": "application/activity+json"},
         )
         with remote_response as r:
             remote_response.raise_for_status()
             extension = music_utils.get_ext_from_type(self.audio_mimetype)
-            title = ' - '.join([self.title, self.album_title, self.artist_name])
-            filename = '{}.{}'.format(title, extension)
+            title = " - ".join([self.title, self.album_title, self.artist_name])
+            filename = "{}.{}".format(title, extension)
             tmp_file = tempfile.TemporaryFile()
             for chunk in r.iter_content(chunk_size=512):
                 tmp_file.write(chunk)
diff --git a/api/funkwhale_api/federation/parsers.py b/api/funkwhale_api/federation/parsers.py
index 874d808f97..8afe21a23a 100644
--- a/api/funkwhale_api/federation/parsers.py
+++ b/api/funkwhale_api/federation/parsers.py
@@ -2,4 +2,4 @@ from rest_framework import parsers
 
 
 class ActivityParser(parsers.JSONParser):
-    media_type = 'application/activity+json'
+    media_type = "application/activity+json"
diff --git a/api/funkwhale_api/federation/permissions.py b/api/funkwhale_api/federation/permissions.py
index 438b675cb3..ccffb1014c 100644
--- a/api/funkwhale_api/federation/permissions.py
+++ b/api/funkwhale_api/federation/permissions.py
@@ -7,15 +7,13 @@ from . import actors
 
 
 class LibraryFollower(BasePermission):
-
     def has_permission(self, request, view):
-        if not preferences.get('federation__music_needs_approval'):
+        if not preferences.get("federation__music_needs_approval"):
             return True
 
-        actor = getattr(request, 'actor', None)
+        actor = getattr(request, "actor", None)
         if actor is None:
             return False
 
-        library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-        return library.received_follows.filter(
-            approved=True, actor=actor).exists()
+        library = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+        return library.received_follows.filter(approved=True, actor=actor).exists()
diff --git a/api/funkwhale_api/federation/renderers.py b/api/funkwhale_api/federation/renderers.py
index 642b634628..d72c4c06a2 100644
--- a/api/funkwhale_api/federation/renderers.py
+++ b/api/funkwhale_api/federation/renderers.py
@@ -2,8 +2,8 @@ from rest_framework.renderers import JSONRenderer
 
 
 class ActivityPubRenderer(JSONRenderer):
-    media_type = 'application/activity+json'
+    media_type = "application/activity+json"
 
 
 class WebfingerRenderer(JSONRenderer):
-    media_type = 'application/jrd+json'
+    media_type = "application/jrd+json"
diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py
index 6ffffaa9aa..367ca99296 100644
--- a/api/funkwhale_api/federation/serializers.py
+++ b/api/funkwhale_api/federation/serializers.py
@@ -20,8 +20,8 @@ from . import utils
 
 
 AP_CONTEXT = [
-    'https://www.w3.org/ns/activitystreams',
-    'https://w3id.org/security/v1',
+    "https://www.w3.org/ns/activitystreams",
+    "https://w3id.org/security/v1",
     {},
 ]
 
@@ -43,58 +43,58 @@ class ActorSerializer(serializers.Serializer):
 
     def to_representation(self, instance):
         ret = {
-            'id': instance.url,
-            'outbox': instance.outbox_url,
-            'inbox': instance.inbox_url,
-            'preferredUsername': instance.preferred_username,
-            'type': instance.type,
+            "id": instance.url,
+            "outbox": instance.outbox_url,
+            "inbox": instance.inbox_url,
+            "preferredUsername": instance.preferred_username,
+            "type": instance.type,
         }
         if instance.name:
-            ret['name'] = instance.name
+            ret["name"] = instance.name
         if instance.followers_url:
-            ret['followers'] = instance.followers_url
+            ret["followers"] = instance.followers_url
         if instance.following_url:
-            ret['following'] = instance.following_url
+            ret["following"] = instance.following_url
         if instance.summary:
-            ret['summary'] = instance.summary
+            ret["summary"] = instance.summary
         if instance.manually_approves_followers is not None:
-            ret['manuallyApprovesFollowers'] = instance.manually_approves_followers
+            ret["manuallyApprovesFollowers"] = instance.manually_approves_followers
 
-        ret['@context'] = AP_CONTEXT
+        ret["@context"] = AP_CONTEXT
         if instance.public_key:
-            ret['publicKey'] = {
-                'owner': instance.url,
-                'publicKeyPem': instance.public_key,
-                'id': '{}#main-key'.format(instance.url)
+            ret["publicKey"] = {
+                "owner": instance.url,
+                "publicKeyPem": instance.public_key,
+                "id": "{}#main-key".format(instance.url),
             }
-        ret['endpoints'] = {}
+        ret["endpoints"] = {}
         if instance.shared_inbox_url:
-            ret['endpoints']['sharedInbox'] = instance.shared_inbox_url
+            ret["endpoints"]["sharedInbox"] = instance.shared_inbox_url
         return ret
 
     def prepare_missing_fields(self):
         kwargs = {
-            'url': self.validated_data['id'],
-            'outbox_url': self.validated_data['outbox'],
-            'inbox_url': self.validated_data['inbox'],
-            'following_url': self.validated_data.get('following'),
-            'followers_url': self.validated_data.get('followers'),
-            'summary': self.validated_data.get('summary'),
-            'type': self.validated_data['type'],
-            'name': self.validated_data.get('name'),
-            'preferred_username': self.validated_data['preferredUsername'],
+            "url": self.validated_data["id"],
+            "outbox_url": self.validated_data["outbox"],
+            "inbox_url": self.validated_data["inbox"],
+            "following_url": self.validated_data.get("following"),
+            "followers_url": self.validated_data.get("followers"),
+            "summary": self.validated_data.get("summary"),
+            "type": self.validated_data["type"],
+            "name": self.validated_data.get("name"),
+            "preferred_username": self.validated_data["preferredUsername"],
         }
-        maf = self.validated_data.get('manuallyApprovesFollowers')
+        maf = self.validated_data.get("manuallyApprovesFollowers")
         if maf is not None:
-            kwargs['manually_approves_followers'] = maf
-        domain = urllib.parse.urlparse(kwargs['url']).netloc
-        kwargs['domain'] = domain
-        for endpoint, url in self.initial_data.get('endpoints', {}).items():
-            if endpoint == 'sharedInbox':
-                kwargs['shared_inbox_url'] = url
+            kwargs["manually_approves_followers"] = maf
+        domain = urllib.parse.urlparse(kwargs["url"]).netloc
+        kwargs["domain"] = domain
+        for endpoint, url in self.initial_data.get("endpoints", {}).items():
+            if endpoint == "sharedInbox":
+                kwargs["shared_inbox_url"] = url
                 break
         try:
-            kwargs['public_key'] = self.initial_data['publicKey']['publicKeyPem']
+            kwargs["public_key"] = self.initial_data["publicKey"]["publicKeyPem"]
         except KeyError:
             pass
         return kwargs
@@ -106,10 +106,7 @@ class ActorSerializer(serializers.Serializer):
     def save(self, **kwargs):
         d = self.prepare_missing_fields()
         d.update(kwargs)
-        return models.Actor.objects.update_or_create(
-            url=d['url'],
-            defaults=d,
-        )[0]
+        return models.Actor.objects.update_or_create(url=d["url"], defaults=d)[0]
 
     def validate_summary(self, value):
         if value:
@@ -120,35 +117,33 @@ class APIActorSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Actor
         fields = [
-            'id',
-            'url',
-            'creation_date',
-            'summary',
-            'preferred_username',
-            'name',
-            'last_fetch_date',
-            'domain',
-            'type',
-            'manually_approves_followers',
-
+            "id",
+            "url",
+            "creation_date",
+            "summary",
+            "preferred_username",
+            "name",
+            "last_fetch_date",
+            "domain",
+            "type",
+            "manually_approves_followers",
         ]
 
 
 class LibraryActorSerializer(ActorSerializer):
-    url = serializers.ListField(
-        child=serializers.JSONField())
+    url = serializers.ListField(child=serializers.JSONField())
 
     def validate(self, validated_data):
         try:
-            urls = validated_data['url']
+            urls = validated_data["url"]
         except KeyError:
-            raise serializers.ValidationError('Missing URL field')
+            raise serializers.ValidationError("Missing URL field")
 
         for u in urls:
             try:
-                if u['name'] != 'library':
+                if u["name"] != "library":
                     continue
-                validated_data['library_url'] = u['href']
+                validated_data["library_url"] = u["href"]
                 break
             except KeyError:
                 continue
@@ -160,12 +155,12 @@ class APIFollowSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Follow
         fields = [
-            'uuid',
-            'actor',
-            'target',
-            'approved',
-            'creation_date',
-            'modification_date',
+            "uuid",
+            "actor",
+            "target",
+            "approved",
+            "creation_date",
+            "modification_date",
         ]
 
 
@@ -177,19 +172,19 @@ class APILibrarySerializer(serializers.ModelSerializer):
         model = models.Library
 
         read_only_fields = [
-            'actor',
-            'uuid',
-            'url',
-            'tracks_count',
-            'follow',
-            'fetched_date',
-            'modification_date',
-            'creation_date',
+            "actor",
+            "uuid",
+            "url",
+            "tracks_count",
+            "follow",
+            "fetched_date",
+            "modification_date",
+            "creation_date",
         ]
         fields = [
-            'autoimport',
-            'federation_enabled',
-            'download_files',
+            "autoimport",
+            "federation_enabled",
+            "download_files",
         ] + read_only_fields
 
 
@@ -203,24 +198,22 @@ class APILibraryFollowUpdateSerializer(serializers.Serializer):
 
     def validate_follow(self, value):
         from . import actors
-        library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-        qs = models.Follow.objects.filter(
-            pk=value,
-            target=library_actor,
-        )
+
+        library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+        qs = models.Follow.objects.filter(pk=value, target=library_actor)
         try:
             return qs.get()
         except models.Follow.DoesNotExist:
-            raise serializers.ValidationError('Invalid follow')
+            raise serializers.ValidationError("Invalid follow")
 
     def save(self):
-        new_status = self.validated_data['approved']
-        follow = self.validated_data['follow']
+        new_status = self.validated_data["approved"]
+        follow = self.validated_data["follow"]
         if new_status == follow.approved:
             return follow
 
         follow.approved = new_status
-        follow.save(update_fields=['approved', 'modification_date'])
+        follow.save(update_fields=["approved", "modification_date"])
         if new_status:
             activity.accept_follow(follow)
         return follow
@@ -233,19 +226,13 @@ class APILibraryCreateSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = models.Library
-        fields = [
-            'uuid',
-            'actor',
-            'autoimport',
-            'federation_enabled',
-            'download_files',
-        ]
+        fields = ["uuid", "actor", "autoimport", "federation_enabled", "download_files"]
 
     def validate(self, validated_data):
         from . import actors
         from . import library
 
-        actor_url = validated_data['actor']
+        actor_url = validated_data["actor"]
         actor_data = actors.get_actor_data(actor_url)
         acs = LibraryActorSerializer(data=actor_data)
         acs.is_valid(raise_exception=True)
@@ -253,43 +240,39 @@ class APILibraryCreateSerializer(serializers.ModelSerializer):
             actor = models.Actor.objects.get(url=actor_url)
         except models.Actor.DoesNotExist:
             actor = acs.save()
-        library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-        validated_data['follow'] = models.Follow.objects.get_or_create(
-            actor=library_actor,
-            target=actor,
+        library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+        validated_data["follow"] = models.Follow.objects.get_or_create(
+            actor=library_actor, target=actor
         )[0]
-        if validated_data['follow'].approved is None:
+        if validated_data["follow"].approved is None:
             funkwhale_utils.on_commit(
                 activity.deliver,
-                FollowSerializer(validated_data['follow']).data,
-                on_behalf_of=validated_data['follow'].actor,
-                to=[validated_data['follow'].target.url],
+                FollowSerializer(validated_data["follow"]).data,
+                on_behalf_of=validated_data["follow"].actor,
+                to=[validated_data["follow"].target.url],
             )
 
-        library_data = library.get_library_data(
-            acs.validated_data['library_url'])
-        if 'errors' in library_data:
+        library_data = library.get_library_data(acs.validated_data["library_url"])
+        if "errors" in library_data:
             # we pass silently because it may means we require permission
             # before scanning
             pass
-        validated_data['library'] = library_data
-        validated_data['library'].setdefault(
-            'id', acs.validated_data['library_url']
-        )
-        validated_data['actor'] = actor
+        validated_data["library"] = library_data
+        validated_data["library"].setdefault("id", acs.validated_data["library_url"])
+        validated_data["actor"] = actor
         return validated_data
 
     def create(self, validated_data):
         library = models.Library.objects.update_or_create(
-            url=validated_data['library']['id'],
+            url=validated_data["library"]["id"],
             defaults={
-                'actor': validated_data['actor'],
-                'follow': validated_data['follow'],
-                'tracks_count': validated_data['library'].get('totalItems'),
-                'federation_enabled': validated_data['federation_enabled'],
-                'autoimport': validated_data['autoimport'],
-                'download_files': validated_data['download_files'],
-            }
+                "actor": validated_data["actor"],
+                "follow": validated_data["follow"],
+                "tracks_count": validated_data["library"].get("totalItems"),
+                "federation_enabled": validated_data["federation_enabled"],
+                "autoimport": validated_data["autoimport"],
+                "download_files": validated_data["download_files"],
+            },
         )[0]
         return library
 
@@ -301,73 +284,73 @@ class APILibraryTrackSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.LibraryTrack
         fields = [
-            'id',
-            'url',
-            'audio_url',
-            'audio_mimetype',
-            'creation_date',
-            'modification_date',
-            'fetched_date',
-            'published_date',
-            'metadata',
-            'artist_name',
-            'album_title',
-            'title',
-            'library',
-            'local_track_file',
-            'status',
+            "id",
+            "url",
+            "audio_url",
+            "audio_mimetype",
+            "creation_date",
+            "modification_date",
+            "fetched_date",
+            "published_date",
+            "metadata",
+            "artist_name",
+            "album_title",
+            "title",
+            "library",
+            "local_track_file",
+            "status",
         ]
 
     def get_status(self, o):
         try:
             if o.local_track_file is not None:
-                return 'imported'
+                return "imported"
         except music_models.TrackFile.DoesNotExist:
             pass
         for job in o.import_jobs.all():
-            if job.status == 'pending':
-                return 'import_pending'
-        return 'not_imported'
+            if job.status == "pending":
+                return "import_pending"
+        return "not_imported"
 
 
 class FollowSerializer(serializers.Serializer):
     id = serializers.URLField(max_length=500)
     object = serializers.URLField(max_length=500)
     actor = serializers.URLField(max_length=500)
-    type = serializers.ChoiceField(choices=['Follow'])
+    type = serializers.ChoiceField(choices=["Follow"])
 
     def validate_object(self, v):
-        expected = self.context.get('follow_target')
+        expected = self.context.get("follow_target")
         if expected and expected.url != v:
-            raise serializers.ValidationError('Invalid target')
+            raise serializers.ValidationError("Invalid target")
         try:
             return models.Actor.objects.get(url=v)
         except models.Actor.DoesNotExist:
-            raise serializers.ValidationError('Target not found')
+            raise serializers.ValidationError("Target not found")
 
     def validate_actor(self, v):
-        expected = self.context.get('follow_actor')
+        expected = self.context.get("follow_actor")
         if expected and expected.url != v:
-            raise serializers.ValidationError('Invalid actor')
+            raise serializers.ValidationError("Invalid actor")
         try:
             return models.Actor.objects.get(url=v)
         except models.Actor.DoesNotExist:
-            raise serializers.ValidationError('Actor not found')
+            raise serializers.ValidationError("Actor not found")
 
     def save(self, **kwargs):
         return models.Follow.objects.get_or_create(
-            actor=self.validated_data['actor'],
-            target=self.validated_data['object'],
+            actor=self.validated_data["actor"],
+            target=self.validated_data["object"],
             **kwargs,
         )[0]
 
     def to_representation(self, instance):
         return {
-            '@context': AP_CONTEXT,
-            'actor': instance.actor.url,
-            'id': instance.get_federation_url(),
-            'object': instance.target.url,
-            'type': 'Follow'
+            "@context": AP_CONTEXT,
+            "actor": instance.actor.url,
+            "id": instance.get_federation_url(),
+            "object": instance.target.url,
+            "type": "Follow",
         }
         return ret
 
@@ -379,13 +362,13 @@ class APIFollowSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Follow
         fields = [
-            'uuid',
-            'id',
-            'approved',
-            'creation_date',
-            'modification_date',
-            'actor',
-            'target',
+            "uuid",
+            "id",
+            "approved",
+            "creation_date",
+            "modification_date",
+            "actor",
+            "target",
         ]
 
 
@@ -393,84 +376,87 @@ class AcceptFollowSerializer(serializers.Serializer):
     id = serializers.URLField(max_length=500)
     actor = serializers.URLField(max_length=500)
     object = FollowSerializer()
-    type = serializers.ChoiceField(choices=['Accept'])
+    type = serializers.ChoiceField(choices=["Accept"])
 
     def validate_actor(self, v):
-        expected = self.context.get('follow_target')
+        expected = self.context.get("follow_target")
         if expected and expected.url != v:
-            raise serializers.ValidationError('Invalid actor')
+            raise serializers.ValidationError("Invalid actor")
         try:
             return models.Actor.objects.get(url=v)
         except models.Actor.DoesNotExist:
-            raise serializers.ValidationError('Actor not found')
+            raise serializers.ValidationError("Actor not found")
 
     def validate(self, validated_data):
         # we ensure the accept actor actually match the follow target
-        if validated_data['actor'] != validated_data['object']['object']:
-            raise serializers.ValidationError('Actor mismatch')
+        if validated_data["actor"] != validated_data["object"]["object"]:
+            raise serializers.ValidationError("Actor mismatch")
         try:
-            validated_data['follow'] = models.Follow.objects.filter(
-                target=validated_data['actor'],
-                actor=validated_data['object']['actor']
-            ).exclude(approved=True).get()
+            validated_data["follow"] = (
+                models.Follow.objects.filter(
+                    target=validated_data["actor"],
+                    actor=validated_data["object"]["actor"],
+                )
+                .exclude(approved=True)
+                .get()
+            )
         except models.Follow.DoesNotExist:
-            raise serializers.ValidationError('No follow to accept')
+            raise serializers.ValidationError("No follow to accept")
         return validated_data
 
     def to_representation(self, instance):
         return {
             "@context": AP_CONTEXT,
-            "id": instance.get_federation_url() + '/accept',
+            "id": instance.get_federation_url() + "/accept",
             "type": "Accept",
             "actor": instance.target.url,
-            "object": FollowSerializer(instance).data
+            "object": FollowSerializer(instance).data,
         }
 
     def save(self):
-        self.validated_data['follow'].approved = True
-        self.validated_data['follow'].save()
-        return self.validated_data['follow']
+        self.validated_data["follow"].approved = True
+        self.validated_data["follow"].save()
+        return self.validated_data["follow"]
 
 
 class UndoFollowSerializer(serializers.Serializer):
     id = serializers.URLField(max_length=500)
     actor = serializers.URLField(max_length=500)
     object = FollowSerializer()
-    type = serializers.ChoiceField(choices=['Undo'])
+    type = serializers.ChoiceField(choices=["Undo"])
 
     def validate_actor(self, v):
-        expected = self.context.get('follow_target')
+        expected = self.context.get("follow_target")
         if expected and expected.url != v:
-            raise serializers.ValidationError('Invalid actor')
+            raise serializers.ValidationError("Invalid actor")
         try:
             return models.Actor.objects.get(url=v)
         except models.Actor.DoesNotExist:
-            raise serializers.ValidationError('Actor not found')
+            raise serializers.ValidationError("Actor not found")
 
     def validate(self, validated_data):
         # we ensure the accept actor actually match the follow actor
-        if validated_data['actor'] != validated_data['object']['actor']:
-            raise serializers.ValidationError('Actor mismatch')
+        if validated_data["actor"] != validated_data["object"]["actor"]:
+            raise serializers.ValidationError("Actor mismatch")
         try:
-            validated_data['follow'] = models.Follow.objects.filter(
-                actor=validated_data['actor'],
-                target=validated_data['object']['object']
+            validated_data["follow"] = models.Follow.objects.filter(
+                actor=validated_data["actor"], target=validated_data["object"]["object"]
             ).get()
         except models.Follow.DoesNotExist:
-            raise serializers.ValidationError('No follow to remove')
+            raise serializers.ValidationError("No follow to remove")
         return validated_data
 
     def to_representation(self, instance):
         return {
             "@context": AP_CONTEXT,
-            "id": instance.get_federation_url() + '/undo',
+            "id": instance.get_federation_url() + "/undo",
             "type": "Undo",
             "actor": instance.actor.url,
-            "object": FollowSerializer(instance).data
+            "object": FollowSerializer(instance).data,
         }
 
     def save(self):
-        return self.validated_data['follow'].delete()
+        return self.validated_data["follow"].delete()
 
 
 class ActorWebfingerSerializer(serializers.Serializer):
@@ -480,68 +466,59 @@ class ActorWebfingerSerializer(serializers.Serializer):
     actor_url = serializers.URLField(max_length=500, required=False)
 
     def validate(self, validated_data):
-        validated_data['actor_url'] = None
-        for l in validated_data['links']:
+        validated_data["actor_url"] = None
+        for l in validated_data["links"]:
             try:
-                if not l['rel'] == 'self':
+                if not l["rel"] == "self":
                     continue
-                if not l['type'] == 'application/activity+json':
+                if not l["type"] == "application/activity+json":
                     continue
-                validated_data['actor_url'] = l['href']
+                validated_data["actor_url"] = l["href"]
                 break
             except KeyError:
                 pass
-        if validated_data['actor_url'] is None:
-            raise serializers.ValidationError('No valid actor url found')
+        if validated_data["actor_url"] is None:
+            raise serializers.ValidationError("No valid actor url found")
         return validated_data
 
     def to_representation(self, instance):
         data = {}
-        data['subject'] = 'acct:{}'.format(instance.webfinger_subject)
-        data['links'] = [
-            {
-                'rel': 'self',
-                'href': instance.url,
-                'type': 'application/activity+json'
-            }
-        ]
-        data['aliases'] = [
-            instance.url
+        data["subject"] = "acct:{}".format(instance.webfinger_subject)
+        data["links"] = [
+            {"rel": "self", "href": instance.url, "type": "application/activity+json"}
         ]
+        data["aliases"] = [instance.url]
         return data
 
 
 class ActivitySerializer(serializers.Serializer):
     actor = serializers.URLField(max_length=500)
     id = serializers.URLField(max_length=500, required=False)
-    type = serializers.ChoiceField(
-        choices=[(c, c) for c in activity.ACTIVITY_TYPES])
+    type = serializers.ChoiceField(choices=[(c, c) for c in activity.ACTIVITY_TYPES])
     object = serializers.JSONField()
 
     def validate_object(self, value):
         try:
-            type = value['type']
+            type = value["type"]
         except KeyError:
-            raise serializers.ValidationError('Missing object type')
+            raise serializers.ValidationError("Missing object type")
         except TypeError:
             # probably a URL
             return value
         try:
             object_serializer = OBJECT_SERIALIZERS[type]
         except KeyError:
-            raise serializers.ValidationError(
-                'Unsupported type {}'.format(type))
+            raise serializers.ValidationError("Unsupported type {}".format(type))
 
         serializer = object_serializer(data=value)
         serializer.is_valid(raise_exception=True)
         return serializer.data
 
     def validate_actor(self, value):
-        request_actor = self.context.get('actor')
+        request_actor = self.context.get("actor")
         if request_actor and request_actor.url != value:
             raise serializers.ValidationError(
-                'The actor making the request do not match'
-                ' the activity actor'
+                "The actor making the request do not match" " the activity actor"
             )
         return value
 
@@ -549,47 +526,39 @@ class ActivitySerializer(serializers.Serializer):
         d = {}
         d.update(conf)
 
-        if self.context.get('include_ap_context', True):
-            d['@context'] = AP_CONTEXT
+        if self.context.get("include_ap_context", True):
+            d["@context"] = AP_CONTEXT
         return d
 
 
 class ObjectSerializer(serializers.Serializer):
     id = serializers.URLField(max_length=500)
     url = serializers.URLField(max_length=500, required=False, allow_null=True)
-    type = serializers.ChoiceField(
-        choices=[(c, c) for c in activity.OBJECT_TYPES])
-    content = serializers.CharField(
-        required=False, allow_null=True)
-    summary = serializers.CharField(
-        required=False, allow_null=True)
-    name = serializers.CharField(
-        required=False, allow_null=True)
-    published = serializers.DateTimeField(
-        required=False, allow_null=True)
-    updated = serializers.DateTimeField(
-        required=False, allow_null=True)
+    type = serializers.ChoiceField(choices=[(c, c) for c in activity.OBJECT_TYPES])
+    content = serializers.CharField(required=False, allow_null=True)
+    summary = serializers.CharField(required=False, allow_null=True)
+    name = serializers.CharField(required=False, allow_null=True)
+    published = serializers.DateTimeField(required=False, allow_null=True)
+    updated = serializers.DateTimeField(required=False, allow_null=True)
     to = serializers.ListField(
-        child=serializers.URLField(max_length=500),
-        required=False, allow_null=True)
+        child=serializers.URLField(max_length=500), required=False, allow_null=True
+    )
     cc = serializers.ListField(
-        child=serializers.URLField(max_length=500),
-        required=False, allow_null=True)
+        child=serializers.URLField(max_length=500), required=False, allow_null=True
+    )
     bto = serializers.ListField(
-        child=serializers.URLField(max_length=500),
-        required=False, allow_null=True)
+        child=serializers.URLField(max_length=500), required=False, allow_null=True
+    )
     bcc = serializers.ListField(
-        child=serializers.URLField(max_length=500),
-        required=False, allow_null=True)
+        child=serializers.URLField(max_length=500), required=False, allow_null=True
+    )
+
 
-OBJECT_SERIALIZERS = {
-    t: ObjectSerializer
-    for t in activity.OBJECT_TYPES
-}
+OBJECT_SERIALIZERS = {t: ObjectSerializer for t in activity.OBJECT_TYPES}
 
 
 class PaginatedCollectionSerializer(serializers.Serializer):
-    type = serializers.ChoiceField(choices=['Collection'])
+    type = serializers.ChoiceField(choices=["Collection"])
     totalItems = serializers.IntegerField(min_value=0)
     actor = serializers.URLField(max_length=500)
     id = serializers.URLField(max_length=500)
@@ -597,30 +566,26 @@ class PaginatedCollectionSerializer(serializers.Serializer):
     last = serializers.URLField(max_length=500)
 
     def to_representation(self, conf):
-        paginator = Paginator(
-            conf['items'],
-            conf.get('page_size', 20)
-        )
-        first = funkwhale_utils.set_query_parameter(conf['id'], page=1)
+        paginator = Paginator(conf["items"], conf.get("page_size", 20))
+        first = funkwhale_utils.set_query_parameter(conf["id"], page=1)
         current = first
-        last = funkwhale_utils.set_query_parameter(
-            conf['id'], page=paginator.num_pages)
+        last = funkwhale_utils.set_query_parameter(conf["id"], page=paginator.num_pages)
         d = {
-            'id': conf['id'],
-            'actor': conf['actor'].url,
-            'totalItems': paginator.count,
-            'type': 'Collection',
-            'current': current,
-            'first': first,
-            'last': last,
+            "id": conf["id"],
+            "actor": conf["actor"].url,
+            "totalItems": paginator.count,
+            "type": "Collection",
+            "current": current,
+            "first": first,
+            "last": last,
         }
-        if self.context.get('include_ap_context', True):
-            d['@context'] = AP_CONTEXT
+        if self.context.get("include_ap_context", True):
+            d["@context"] = AP_CONTEXT
         return d
 
 
 class CollectionPageSerializer(serializers.Serializer):
-    type = serializers.ChoiceField(choices=['CollectionPage'])
+    type = serializers.ChoiceField(choices=["CollectionPage"])
     totalItems = serializers.IntegerField(min_value=0)
     items = serializers.ListField()
     actor = serializers.URLField(max_length=500)
@@ -632,7 +597,7 @@ class CollectionPageSerializer(serializers.Serializer):
     partOf = serializers.URLField(max_length=500)
 
     def validate_items(self, v):
-        item_serializer = self.context.get('item_serializer')
+        item_serializer = self.context.get("item_serializer")
         if not item_serializer:
             return v
         raw_items = [item_serializer(data=i, context=self.context) for i in v]
@@ -641,47 +606,45 @@ class CollectionPageSerializer(serializers.Serializer):
             if i.is_valid():
                 valid_items.append(i)
             else:
-                logger.debug('Invalid item %s: %s', i.data, i.errors)
+                logger.debug("Invalid item %s: %s", i.data, i.errors)
 
         return valid_items
 
     def to_representation(self, conf):
-        page = conf['page']
-        first = funkwhale_utils.set_query_parameter(
-            conf['id'], page=1)
+        page = conf["page"]
+        first = funkwhale_utils.set_query_parameter(conf["id"], page=1)
         last = funkwhale_utils.set_query_parameter(
-            conf['id'], page=page.paginator.num_pages)
-        id = funkwhale_utils.set_query_parameter(
-            conf['id'], page=page.number)
+            conf["id"], page=page.paginator.num_pages
+        )
+        id = funkwhale_utils.set_query_parameter(conf["id"], page=page.number)
         d = {
-            'id': id,
-            'partOf': conf['id'],
-            'actor': conf['actor'].url,
-            'totalItems': page.paginator.count,
-            'type': 'CollectionPage',
-            'first': first,
-            'last': last,
-            'items': [
-                conf['item_serializer'](
-                    i,
-                    context={
-                        'actor': conf['actor'],
-                        'include_ap_context': False}
+            "id": id,
+            "partOf": conf["id"],
+            "actor": conf["actor"].url,
+            "totalItems": page.paginator.count,
+            "type": "CollectionPage",
+            "first": first,
+            "last": last,
+            "items": [
+                conf["item_serializer"](
+                    i, context={"actor": conf["actor"], "include_ap_context": False}
                 ).data
                 for i in page.object_list
-            ]
+            ],
         }
 
         if page.has_previous():
-            d['prev'] = funkwhale_utils.set_query_parameter(
-                conf['id'], page=page.previous_page_number())
+            d["prev"] = funkwhale_utils.set_query_parameter(
+                conf["id"], page=page.previous_page_number()
+            )
 
         if page.has_next():
-            d['next'] = funkwhale_utils.set_query_parameter(
-                conf['id'], page=page.next_page_number())
+            d["next"] = funkwhale_utils.set_query_parameter(
+                conf["id"], page=page.next_page_number()
+            )
 
-        if self.context.get('include_ap_context', True):
-            d['@context'] = AP_CONTEXT
+        if self.context.get("include_ap_context", True):
+            d["@context"] = AP_CONTEXT
         return d
 
 
@@ -704,12 +667,9 @@ class AudioMetadataSerializer(serializers.Serializer):
     artist = ArtistMetadataSerializer()
     release = ReleaseMetadataSerializer()
     recording = RecordingMetadataSerializer()
-    bitrate = serializers.IntegerField(
-        required=False, allow_null=True, min_value=0)
-    size = serializers.IntegerField(
-        required=False, allow_null=True, min_value=0)
-    length = serializers.IntegerField(
-        required=False, allow_null=True, min_value=0)
+    bitrate = serializers.IntegerField(required=False, allow_null=True, min_value=0)
+    size = serializers.IntegerField(required=False, allow_null=True, min_value=0)
+    length = serializers.IntegerField(required=False, allow_null=True, min_value=0)
 
 
 class AudioSerializer(serializers.Serializer):
@@ -721,41 +681,39 @@ class AudioSerializer(serializers.Serializer):
     metadata = AudioMetadataSerializer()
 
     def validate_type(self, v):
-        if v != 'Audio':
-            raise serializers.ValidationError('Invalid type for audio')
+        if v != "Audio":
+            raise serializers.ValidationError("Invalid type for audio")
         return v
 
     def validate_url(self, v):
         try:
-            url = v['href']
+            url = v["href"]
         except (KeyError, TypeError):
-            raise serializers.ValidationError('Missing href')
+            raise serializers.ValidationError("Missing href")
 
         try:
-            media_type = v['mediaType']
+            media_type = v["mediaType"]
         except (KeyError, TypeError):
-            raise serializers.ValidationError('Missing mediaType')
+            raise serializers.ValidationError("Missing mediaType")
 
-        if not media_type or not media_type.startswith('audio/'):
-            raise serializers.ValidationError('Invalid mediaType')
+        if not media_type or not media_type.startswith("audio/"):
+            raise serializers.ValidationError("Invalid mediaType")
 
         return v
 
     def create(self, validated_data):
         defaults = {
-            'audio_mimetype': validated_data['url']['mediaType'],
-            'audio_url': validated_data['url']['href'],
-            'metadata': validated_data['metadata'],
-            'artist_name': validated_data['metadata']['artist']['name'],
-            'album_title': validated_data['metadata']['release']['title'],
-            'title': validated_data['metadata']['recording']['title'],
-            'published_date': validated_data['published'],
-            'modification_date': validated_data.get('updated'),
+            "audio_mimetype": validated_data["url"]["mediaType"],
+            "audio_url": validated_data["url"]["href"],
+            "metadata": validated_data["metadata"],
+            "artist_name": validated_data["metadata"]["artist"]["name"],
+            "album_title": validated_data["metadata"]["release"]["title"],
+            "title": validated_data["metadata"]["recording"]["title"],
+            "published_date": validated_data["published"],
+            "modification_date": validated_data.get("updated"),
         }
         return models.LibraryTrack.objects.get_or_create(
-            library=self.context['library'],
-            url=validated_data['id'],
-            defaults=defaults
+            library=self.context["library"], url=validated_data["id"], defaults=defaults
         )[0]
 
     def to_representation(self, instance):
@@ -764,87 +722,77 @@ class AudioSerializer(serializers.Serializer):
         artist = instance.track.artist
 
         d = {
-            'type': 'Audio',
-            'id': instance.get_federation_url(),
-            'name': instance.track.full_name,
-            'published': instance.creation_date.isoformat(),
-            'updated': instance.modification_date.isoformat(),
-            'metadata': {
-                'artist': {
-                    'musicbrainz_id': str(artist.mbid) if artist.mbid else None,
-                    'name': artist.name,
+            "type": "Audio",
+            "id": instance.get_federation_url(),
+            "name": instance.track.full_name,
+            "published": instance.creation_date.isoformat(),
+            "updated": instance.modification_date.isoformat(),
+            "metadata": {
+                "artist": {
+                    "musicbrainz_id": str(artist.mbid) if artist.mbid else None,
+                    "name": artist.name,
                 },
-                'release': {
-                    'musicbrainz_id': str(album.mbid) if album.mbid else None,
-                    'title': album.title,
+                "release": {
+                    "musicbrainz_id": str(album.mbid) if album.mbid else None,
+                    "title": album.title,
                 },
-                'recording': {
-                    'musicbrainz_id': str(track.mbid) if track.mbid else None,
-                    'title': track.title,
+                "recording": {
+                    "musicbrainz_id": str(track.mbid) if track.mbid else None,
+                    "title": track.title,
                 },
-                'bitrate': instance.bitrate,
-                'size': instance.size,
-                'length': instance.duration,
+                "bitrate": instance.bitrate,
+                "size": instance.size,
+                "length": instance.duration,
             },
-            'url': {
-                'href': utils.full_url(instance.path),
-                'type': 'Link',
-                'mediaType': instance.mimetype
+            "url": {
+                "href": utils.full_url(instance.path),
+                "type": "Link",
+                "mediaType": instance.mimetype,
             },
-            'attributedTo': [
-                self.context['actor'].url
-            ]
+            "attributedTo": [self.context["actor"].url],
         }
-        if self.context.get('include_ap_context', True):
-            d['@context'] = AP_CONTEXT
+        if self.context.get("include_ap_context", True):
+            d["@context"] = AP_CONTEXT
         return d
 
 
 class CollectionSerializer(serializers.Serializer):
-
     def to_representation(self, conf):
         d = {
-            'id': conf['id'],
-            'actor': conf['actor'].url,
-            'totalItems': len(conf['items']),
-            'type': 'Collection',
-            'items': [
-                conf['item_serializer'](
-                    i,
-                    context={
-                        'actor': conf['actor'],
-                        'include_ap_context': False}
+            "id": conf["id"],
+            "actor": conf["actor"].url,
+            "totalItems": len(conf["items"]),
+            "type": "Collection",
+            "items": [
+                conf["item_serializer"](
+                    i, context={"actor": conf["actor"], "include_ap_context": False}
                 ).data
-                for i in conf['items']
-            ]
+                for i in conf["items"]
+            ],
         }
 
-        if self.context.get('include_ap_context', True):
-            d['@context'] = AP_CONTEXT
+        if self.context.get("include_ap_context", True):
+            d["@context"] = AP_CONTEXT
         return d
 
 
 class LibraryTrackActionSerializer(common_serializers.ActionSerializer):
-    actions = ['import']
+    actions = ["import"]
     filterset_class = filters.LibraryTrackFilter
 
     @transaction.atomic
     def handle_import(self, objects):
         batch = music_models.ImportBatch.objects.create(
-            source='federation',
-            submitted_by=self.context['submitted_by']
+            source="federation", submitted_by=self.context["submitted_by"]
         )
         jobs = []
         for lt in objects:
             job = music_models.ImportJob(
-                batch=batch,
-                library_track=lt,
-                mbid=lt.mbid,
-                source=lt.url,
+                batch=batch, library_track=lt, mbid=lt.mbid, source=lt.url
             )
             jobs.append(job)
 
         music_models.ImportJob.objects.bulk_create(jobs)
         music_tasks.import_batch_run.delay(import_batch_id=batch.pk)
 
-        return {'batch': {'id': batch.pk}}
+        return {"batch": {"id": batch.pk}}
diff --git a/api/funkwhale_api/federation/signing.py b/api/funkwhale_api/federation/signing.py
index 8d984d3ffd..46e7ae55a3 100644
--- a/api/funkwhale_api/federation/signing.py
+++ b/api/funkwhale_api/federation/signing.py
@@ -10,9 +10,7 @@ logger = logging.getLogger(__name__)
 
 def verify(request, public_key):
     return requests_http_signature.HTTPSignatureAuth.verify(
-        request,
-        key_resolver=lambda **kwargs: public_key,
-        use_auth_header=False,
+        request, key_resolver=lambda **kwargs: public_key, use_auth_header=False
     )
 
 
@@ -27,26 +25,24 @@ def verify_django(django_request, public_key):
         # with requests_http_signature
         headers[h.lower()] = v
     try:
-        signature = headers['Signature']
+        signature = headers["Signature"]
     except KeyError:
         raise exceptions.MissingSignature
-    url = 'http://noop{}'.format(django_request.path)
-    query = django_request.META['QUERY_STRING']
+    url = "http://noop{}".format(django_request.path)
+    query = django_request.META["QUERY_STRING"]
     if query:
-        url += '?{}'.format(query)
+        url += "?{}".format(query)
     signature_headers = signature.split('headers="')[1].split('",')[0]
-    expected = signature_headers.split(' ')
-    logger.debug('Signature expected headers: %s', expected)
+    expected = signature_headers.split(" ")
+    logger.debug("Signature expected headers: %s", expected)
     for header in expected:
         try:
             headers[header]
         except KeyError:
-            logger.debug('Missing header: %s', header)
+            logger.debug("Missing header: %s", header)
     request = requests.Request(
-        method=django_request.method,
-        url=url,
-        data=django_request.body,
-        headers=headers)
+        method=django_request.method, url=url, data=django_request.body, headers=headers
+    )
     for h in request.headers.keys():
         v = request.headers[h]
         if v:
@@ -58,13 +54,8 @@ def verify_django(django_request, public_key):
 def get_auth(private_key, private_key_id):
     return requests_http_signature.HTTPSignatureAuth(
         use_auth_header=False,
-        headers=[
-            '(request-target)',
-            'user-agent',
-            'host',
-            'date',
-            'content-type'],
-        algorithm='rsa-sha256',
-        key=private_key.encode('utf-8'),
+        headers=["(request-target)", "user-agent", "host", "date", "content-type"],
+        algorithm="rsa-sha256",
+        key=private_key.encode("utf-8"),
         key_id=private_key_id,
     )
diff --git a/api/funkwhale_api/federation/tasks.py b/api/funkwhale_api/federation/tasks.py
index 8f931b0ed7..0e6ee1e609 100644
--- a/api/funkwhale_api/federation/tasks.py
+++ b/api/funkwhale_api/federation/tasks.py
@@ -24,96 +24,100 @@ logger = logging.getLogger(__name__)
 
 
 @celery.app.task(
-    name='federation.send',
+    name="federation.send",
     autoretry_for=[RequestException],
     retry_backoff=30,
-    max_retries=5)
-@celery.require_instance(models.Actor, 'actor')
+    max_retries=5,
+)
+@celery.require_instance(models.Actor, "actor")
 def send(activity, actor, to):
-    logger.info('Preparing activity delivery to %s', to)
-    auth = signing.get_auth(
-        actor.private_key, actor.private_key_id)
+    logger.info("Preparing activity delivery to %s", to)
+    auth = signing.get_auth(actor.private_key, actor.private_key_id)
     for url in to:
         recipient_actor = actors.get_actor(url)
-        logger.debug('delivering to %s', recipient_actor.inbox_url)
-        logger.debug('activity content: %s', json.dumps(activity))
+        logger.debug("delivering to %s", recipient_actor.inbox_url)
+        logger.debug("activity content: %s", json.dumps(activity))
         response = session.get_session().post(
             auth=auth,
             json=activity,
             url=recipient_actor.inbox_url,
             timeout=5,
             verify=settings.EXTERNAL_REQUESTS_VERIFY_SSL,
-            headers={
-                'Content-Type': 'application/activity+json'
-            }
+            headers={"Content-Type": "application/activity+json"},
         )
         response.raise_for_status()
-        logger.debug('Remote answered with %s', response.status_code)
+        logger.debug("Remote answered with %s", response.status_code)
 
 
 @celery.app.task(
-    name='federation.scan_library',
+    name="federation.scan_library",
     autoretry_for=[RequestException],
     retry_backoff=30,
-    max_retries=5)
-@celery.require_instance(models.Library, 'library')
+    max_retries=5,
+)
+@celery.require_instance(models.Library, "library")
 def scan_library(library, until=None):
     if not library.federation_enabled:
         return
 
     data = lb.get_library_data(library.url)
-    scan_library_page.delay(
-        library_id=library.id, page_url=data['first'], until=until)
+    scan_library_page.delay(library_id=library.id, page_url=data["first"], until=until)
     library.fetched_date = timezone.now()
-    library.tracks_count = data['totalItems']
-    library.save(update_fields=['fetched_date', 'tracks_count'])
+    library.tracks_count = data["totalItems"]
+    library.save(update_fields=["fetched_date", "tracks_count"])
 
 
 @celery.app.task(
-    name='federation.scan_library_page',
+    name="federation.scan_library_page",
     autoretry_for=[RequestException],
     retry_backoff=30,
-    max_retries=5)
-@celery.require_instance(models.Library, 'library')
+    max_retries=5,
+)
+@celery.require_instance(models.Library, "library")
 def scan_library_page(library, page_url, until=None):
     if not library.federation_enabled:
         return
 
     data = lb.get_library_page(library, page_url)
     lts = []
-    for item_serializer in data['items']:
-        item_date = item_serializer.validated_data['published']
+    for item_serializer in data["items"]:
+        item_date = item_serializer.validated_data["published"]
         if until and item_date < until:
             return
         lts.append(item_serializer.save())
 
-    next_page = data.get('next')
+    next_page = data.get("next")
     if next_page and next_page != page_url:
         scan_library_page.delay(library_id=library.id, page_url=next_page)
 
 
-@celery.app.task(name='federation.clean_music_cache')
+@celery.app.task(name="federation.clean_music_cache")
 def clean_music_cache():
     preferences = global_preferences_registry.manager()
-    delay = preferences['federation__music_cache_duration']
+    delay = preferences["federation__music_cache_duration"]
     if delay < 1:
         return  # cache clearing disabled
     limit = timezone.now() - datetime.timedelta(minutes=delay)
 
-    candidates = models.LibraryTrack.objects.filter(
-        Q(audio_file__isnull=False) & (
-            Q(local_track_file__accessed_date__lt=limit) |
-            Q(local_track_file__accessed_date=None)
+    candidates = (
+        models.LibraryTrack.objects.filter(
+            Q(audio_file__isnull=False)
+            & (
+                Q(local_track_file__accessed_date__lt=limit)
+                | Q(local_track_file__accessed_date=None)
+            )
         )
-    ).exclude(audio_file='').only('audio_file', 'id')
+        .exclude(audio_file="")
+        .only("audio_file", "id")
+    )
     for lt in candidates:
         lt.audio_file.delete()
 
     # we also delete orphaned files, if any
-    storage = models.LibraryTrack._meta.get_field('audio_file').storage
-    files = get_files(storage, 'federation_cache')
+    storage = models.LibraryTrack._meta.get_field("audio_file").storage
+    files = get_files(storage, "federation_cache")
     existing = models.LibraryTrack.objects.filter(audio_file__in=files)
-    missing = set(files) - set(existing.values_list('audio_file', flat=True))
+    missing = set(files) - set(existing.values_list("audio_file", flat=True))
     for m in missing:
         storage.delete(m)
 
@@ -124,12 +128,9 @@ def get_files(storage, *parts):
     in a given directory using django's storage.
     """
     if not parts:
-        raise ValueError('Missing path')
+        raise ValueError("Missing path")
 
     dirs, files = storage.listdir(os.path.join(*parts))
     for dir in dirs:
         files += get_files(storage, *(list(parts) + [dir]))
-    return [
-        os.path.join(parts[-1], path)
-        for path in files
-    ]
+    return [os.path.join(parts[-1], path) for path in files]
diff --git a/api/funkwhale_api/federation/urls.py b/api/funkwhale_api/federation/urls.py
index 2c24b5257e..3cd4cb7329 100644
--- a/api/funkwhale_api/federation/urls.py
+++ b/api/funkwhale_api/federation/urls.py
@@ -6,19 +6,11 @@ from . import views
 router = routers.SimpleRouter(trailing_slash=False)
 music_router = routers.SimpleRouter(trailing_slash=False)
 router.register(
-    r'federation/instance/actors',
-    views.InstanceActorViewSet,
-    'instance-actors')
-router.register(
-    r'.well-known',
-    views.WellKnownViewSet,
-    'well-known')
-
-music_router.register(
-    r'files',
-    views.MusicFilesViewSet,
-    'files',
+    r"federation/instance/actors", views.InstanceActorViewSet, "instance-actors"
 )
+router.register(r".well-known", views.WellKnownViewSet, "well-known")
+
+music_router.register(r"files", views.MusicFilesViewSet, "files")
 urlpatterns = router.urls + [
-    url('federation/music/', include((music_router.urls, 'music'), namespace='music'))
+    url("federation/music/", include((music_router.urls, "music"), namespace="music"))
 ]
diff --git a/api/funkwhale_api/federation/utils.py b/api/funkwhale_api/federation/utils.py
index df093add8f..e09870223d 100644
--- a/api/funkwhale_api/federation/utils.py
+++ b/api/funkwhale_api/federation/utils.py
@@ -6,10 +6,10 @@ def full_url(path):
     Given a relative path, return a full url usable for federation purpose
     """
     root = settings.FUNKWHALE_URL
-    if path.startswith('/') and root.endswith('/'):
+    if path.startswith("/") and root.endswith("/"):
         return root + path[1:]
-    elif not path.startswith('/') and not root.endswith('/'):
-        return root + '/' + path
+    elif not path.startswith("/") and not root.endswith("/"):
+        return root + "/" + path
     else:
         return root + path
 
@@ -19,17 +19,14 @@ def clean_wsgi_headers(raw_headers):
     Convert WSGI headers from CONTENT_TYPE to Content-Type notation
     """
     cleaned = {}
-    non_prefixed = [
-        'content_type',
-        'content_length',
-    ]
+    non_prefixed = ["content_type", "content_length"]
     for raw_header, value in raw_headers.items():
         h = raw_header.lower()
-        if not h.startswith('http_') and h not in non_prefixed:
+        if not h.startswith("http_") and h not in non_prefixed:
             continue
 
-        words = h.replace('http_', '', 1).split('_')
-        cleaned_header = '-'.join([w.capitalize() for w in words])
+        words = h.replace("http_", "", 1).split("_")
+        cleaned_header = "-".join([w.capitalize() for w in words])
         cleaned[cleaned_header] = value
 
     return cleaned
diff --git a/api/funkwhale_api/federation/views.py b/api/funkwhale_api/federation/views.py
index 1350ec731e..36bf1a4443 100644
--- a/api/funkwhale_api/federation/views.py
+++ b/api/funkwhale_api/federation/views.py
@@ -34,22 +34,21 @@ from . import webfinger
 
 class FederationMixin(object):
     def dispatch(self, request, *args, **kwargs):
-        if not preferences.get('federation__enabled'):
+        if not preferences.get("federation__enabled"):
             return HttpResponse(status=405)
         return super().dispatch(request, *args, **kwargs)
 
 
 class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet):
-    lookup_field = 'actor'
-    lookup_value_regex = '[a-z]*'
-    authentication_classes = [
-        authentication.SignatureAuthentication]
+    lookup_field = "actor"
+    lookup_value_regex = "[a-z]*"
+    authentication_classes = [authentication.SignatureAuthentication]
     permission_classes = []
     renderer_classes = [renderers.ActivityPubRenderer]
 
     def get_object(self):
         try:
-            return actors.SYSTEM_ACTORS[self.kwargs['actor']]
+            return actors.SYSTEM_ACTORS[self.kwargs["actor"]]
         except KeyError:
             raise Http404
 
@@ -59,12 +58,10 @@ class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet):
         data = actor.system_conf.serialize()
         return response.Response(data, status=200)
 
-    @detail_route(methods=['get', 'post'])
+    @detail_route(methods=["get", "post"])
     def inbox(self, request, *args, **kwargs):
         system_actor = self.get_object()
-        handler = getattr(system_actor, '{}_inbox'.format(
-            request.method.lower()
-        ))
+        handler = getattr(system_actor, "{}_inbox".format(request.method.lower()))
 
         try:
             data = handler(request.data, actor=request.actor)
@@ -72,12 +69,10 @@ class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet):
             return response.Response(status=405)
         return response.Response({}, status=200)
 
-    @detail_route(methods=['get', 'post'])
+    @detail_route(methods=["get", "post"])
     def outbox(self, request, *args, **kwargs):
         system_actor = self.get_object()
-        handler = getattr(system_actor, '{}_outbox'.format(
-            request.method.lower()
-        ))
+        handler = getattr(system_actor, "{}_outbox".format(request.method.lower()))
         try:
             data = handler(request.data, actor=request.actor)
         except NotImplementedError:
@@ -90,45 +85,36 @@ class WellKnownViewSet(viewsets.GenericViewSet):
     permission_classes = []
     renderer_classes = [renderers.JSONRenderer, renderers.WebfingerRenderer]
 
-    @list_route(methods=['get'])
+    @list_route(methods=["get"])
     def nodeinfo(self, request, *args, **kwargs):
-        if not preferences.get('instance__nodeinfo_enabled'):
+        if not preferences.get("instance__nodeinfo_enabled"):
             return HttpResponse(status=404)
         data = {
-            'links': [
+            "links": [
                 {
-                    'rel': 'http://nodeinfo.diaspora.software/ns/schema/2.0',
-                    'href': utils.full_url(
-                        reverse('api:v1:instance:nodeinfo-2.0')
-                    )
+                    "rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
+                    "href": utils.full_url(reverse("api:v1:instance:nodeinfo-2.0")),
                 }
             ]
         }
         return response.Response(data)
 
-    @list_route(methods=['get'])
+    @list_route(methods=["get"])
     def webfinger(self, request, *args, **kwargs):
-        if not preferences.get('federation__enabled'):
+        if not preferences.get("federation__enabled"):
             return HttpResponse(status=405)
         try:
-            resource_type, resource = webfinger.clean_resource(
-                request.GET['resource'])
-            cleaner = getattr(webfinger, 'clean_{}'.format(resource_type))
+            resource_type, resource = webfinger.clean_resource(request.GET["resource"])
+            cleaner = getattr(webfinger, "clean_{}".format(resource_type))
             result = cleaner(resource)
         except forms.ValidationError as e:
-            return response.Response({
-                'errors': {
-                    'resource': e.message
-                }
-            }, status=400)
+            return response.Response({"errors": {"resource": e.message}}, status=400)
         except KeyError:
-            return response.Response({
-                'errors': {
-                    'resource': 'This field is required',
-                }
-            }, status=400)
+            return response.Response(
+                {"errors": {"resource": "This field is required"}}, status=400
+            )
 
-        handler = getattr(self, 'handler_{}'.format(resource_type))
+        handler = getattr(self, "handler_{}".format(resource_type))
         data = handler(result)
 
         return response.Response(data)
@@ -140,28 +126,25 @@ class WellKnownViewSet(viewsets.GenericViewSet):
 
 
 class MusicFilesViewSet(FederationMixin, viewsets.GenericViewSet):
-    authentication_classes = [
-        authentication.SignatureAuthentication]
+    authentication_classes = [authentication.SignatureAuthentication]
     permission_classes = [permissions.LibraryFollower]
     renderer_classes = [renderers.ActivityPubRenderer]
 
     def list(self, request, *args, **kwargs):
-        page = request.GET.get('page')
-        library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-        qs = music_models.TrackFile.objects.order_by(
-            '-creation_date'
-        ).select_related(
-            'track__artist',
-            'track__album__artist'
-        ).filter(library_track__isnull=True)
+        page = request.GET.get("page")
+        library = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+        qs = (
+            music_models.TrackFile.objects.order_by("-creation_date")
+            .select_related("track__artist", "track__album__artist")
+            .filter(library_track__isnull=True)
+        )
         if page is None:
             conf = {
-                'id': utils.full_url(reverse('federation:music:files-list')),
-                'page_size': preferences.get(
-                    'federation__collection_page_size'),
-                'items': qs,
-                'item_serializer': serializers.AudioSerializer,
-                'actor': library,
+                "id": utils.full_url(reverse("federation:music:files-list")),
+                "page_size": preferences.get("federation__collection_page_size"),
+                "items": qs,
+                "item_serializer": serializers.AudioSerializer,
+                "actor": library,
             }
             serializer = serializers.PaginatedCollectionSerializer(conf)
             data = serializer.data
@@ -169,17 +152,17 @@ class MusicFilesViewSet(FederationMixin, viewsets.GenericViewSet):
             try:
                 page_number = int(page)
             except:
-                return response.Response(
-                    {'page': ['Invalid page number']}, status=400)
+                return response.Response({"page": ["Invalid page number"]}, status=400)
             p = paginator.Paginator(
-                qs, preferences.get('federation__collection_page_size'))
+                qs, preferences.get("federation__collection_page_size")
+            )
             try:
                 page = p.page(page_number)
                 conf = {
-                    'id': utils.full_url(reverse('federation:music:files-list')),
-                    'page': page,
-                    'item_serializer': serializers.AudioSerializer,
-                    'actor': library,
+                    "id": utils.full_url(reverse("federation:music:files-list")),
+                    "page": page,
+                    "item_serializer": serializers.AudioSerializer,
+                    "actor": library,
                 }
                 serializer = serializers.CollectionPageSerializer(conf)
                 data = serializer.data
@@ -190,93 +173,76 @@ class MusicFilesViewSet(FederationMixin, viewsets.GenericViewSet):
 
 
 class LibraryViewSet(
-        mixins.RetrieveModelMixin,
-        mixins.UpdateModelMixin,
-        mixins.ListModelMixin,
-        viewsets.GenericViewSet):
+    mixins.RetrieveModelMixin,
+    mixins.UpdateModelMixin,
+    mixins.ListModelMixin,
+    viewsets.GenericViewSet,
+):
     permission_classes = (HasUserPermission,)
-    required_permissions = ['federation']
-    queryset = models.Library.objects.all().select_related(
-        'actor',
-        'follow',
-    )
-    lookup_field = 'uuid'
+    required_permissions = ["federation"]
+    queryset = models.Library.objects.all().select_related("actor", "follow")
+    lookup_field = "uuid"
     filter_class = filters.LibraryFilter
     serializer_class = serializers.APILibrarySerializer
     ordering_fields = (
-        'id',
-        'creation_date',
-        'fetched_date',
-        'actor__domain',
-        'tracks_count',
+        "id",
+        "creation_date",
+        "fetched_date",
+        "actor__domain",
+        "tracks_count",
     )
 
-    @list_route(methods=['get'])
+    @list_route(methods=["get"])
     def fetch(self, request, *args, **kwargs):
-        account = request.GET.get('account')
+        account = request.GET.get("account")
         if not account:
-            return response.Response(
-                {'account': 'This field is mandatory'}, status=400)
+            return response.Response({"account": "This field is mandatory"}, status=400)
 
         data = library.scan_from_account_name(account)
         return response.Response(data)
 
-    @detail_route(methods=['post'])
+    @detail_route(methods=["post"])
     def scan(self, request, *args, **kwargs):
         library = self.get_object()
-        serializer = serializers.APILibraryScanSerializer(
-            data=request.data
-        )
+        serializer = serializers.APILibraryScanSerializer(data=request.data)
         serializer.is_valid(raise_exception=True)
         result = tasks.scan_library.delay(
-            library_id=library.pk,
-            until=serializer.validated_data.get('until')
+            library_id=library.pk, until=serializer.validated_data.get("until")
         )
-        return response.Response({'task': result.id})
+        return response.Response({"task": result.id})
 
-    @list_route(methods=['get'])
+    @list_route(methods=["get"])
     def following(self, request, *args, **kwargs):
-        library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-        queryset = models.Follow.objects.filter(
-            actor=library_actor
-        ).select_related(
-            'actor',
-            'target',
-        ).order_by('-creation_date')
+        library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+        queryset = (
+            models.Follow.objects.filter(actor=library_actor)
+            .select_related("actor", "target")
+            .order_by("-creation_date")
+        )
         filterset = filters.FollowFilter(request.GET, queryset=queryset)
         final_qs = filterset.qs
         serializer = serializers.APIFollowSerializer(final_qs, many=True)
-        data = {
-            'results': serializer.data,
-            'count': len(final_qs),
-        }
+        data = {"results": serializer.data, "count": len(final_qs)}
         return response.Response(data)
 
-    @list_route(methods=['get', 'patch'])
+    @list_route(methods=["get", "patch"])
     def followers(self, request, *args, **kwargs):
-        if request.method.lower() == 'patch':
-            serializer = serializers.APILibraryFollowUpdateSerializer(
-                data=request.data)
+        if request.method.lower() == "patch":
+            serializer = serializers.APILibraryFollowUpdateSerializer(data=request.data)
             serializer.is_valid(raise_exception=True)
             follow = serializer.save()
-            return response.Response(
-                serializers.APIFollowSerializer(follow).data
-            )
+            return response.Response(serializers.APIFollowSerializer(follow).data)
 
-        library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-        queryset = models.Follow.objects.filter(
-            target=library_actor
-        ).select_related(
-            'actor',
-            'target',
-        ).order_by('-creation_date')
+        library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+        queryset = (
+            models.Follow.objects.filter(target=library_actor)
+            .select_related("actor", "target")
+            .order_by("-creation_date")
+        )
         filterset = filters.FollowFilter(request.GET, queryset=queryset)
         final_qs = filterset.qs
         serializer = serializers.APIFollowSerializer(final_qs, many=True)
-        data = {
-            'results': serializer.data,
-            'count': len(final_qs),
-        }
+        data = {"results": serializer.data, "count": len(final_qs)}
         return response.Response(data)
 
     @transaction.atomic
@@ -287,37 +253,32 @@ class LibraryViewSet(
         return response.Response(serializer.data, status=201)
 
 
-class LibraryTrackViewSet(
-        mixins.ListModelMixin,
-        viewsets.GenericViewSet):
+class LibraryTrackViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
     permission_classes = (HasUserPermission,)
-    required_permissions = ['federation']
-    queryset = models.LibraryTrack.objects.all().select_related(
-        'library__actor',
-        'library__follow',
-        'local_track_file',
-    ).prefetch_related('import_jobs')
+    required_permissions = ["federation"]
+    queryset = (
+        models.LibraryTrack.objects.all()
+        .select_related("library__actor", "library__follow", "local_track_file")
+        .prefetch_related("import_jobs")
+    )
     filter_class = filters.LibraryTrackFilter
     serializer_class = serializers.APILibraryTrackSerializer
     ordering_fields = (
-        'id',
-        'artist_name',
-        'title',
-        'album_title',
-        'creation_date',
-        'modification_date',
-        'fetched_date',
-        'published_date',
+        "id",
+        "artist_name",
+        "title",
+        "album_title",
+        "creation_date",
+        "modification_date",
+        "fetched_date",
+        "published_date",
     )
 
-    @list_route(methods=['post'])
+    @list_route(methods=["post"])
     def action(self, request, *args, **kwargs):
-        queryset = models.LibraryTrack.objects.filter(
-            local_track_file__isnull=True)
+        queryset = models.LibraryTrack.objects.filter(local_track_file__isnull=True)
         serializer = serializers.LibraryTrackActionSerializer(
-            request.data,
-            queryset=queryset,
-            context={'submitted_by': request.user}
+            request.data, queryset=queryset, context={"submitted_by": request.user}
         )
         serializer.is_valid(raise_exception=True)
         result = serializer.save()
diff --git a/api/funkwhale_api/federation/webfinger.py b/api/funkwhale_api/federation/webfinger.py
index f5cb996359..b37f02e8a8 100644
--- a/api/funkwhale_api/federation/webfinger.py
+++ b/api/funkwhale_api/federation/webfinger.py
@@ -8,36 +8,35 @@ from . import actors
 from . import utils
 from . import serializers
 
-VALID_RESOURCE_TYPES = ['acct']
+VALID_RESOURCE_TYPES = ["acct"]
 
 
 def clean_resource(resource_string):
     if not resource_string:
-        raise forms.ValidationError('Invalid resource string')
+        raise forms.ValidationError("Invalid resource string")
 
     try:
-        resource_type, resource = resource_string.split(':', 1)
+        resource_type, resource = resource_string.split(":", 1)
     except ValueError:
-        raise forms.ValidationError('Missing webfinger resource type')
+        raise forms.ValidationError("Missing webfinger resource type")
 
     if resource_type not in VALID_RESOURCE_TYPES:
-        raise forms.ValidationError('Invalid webfinger resource type')
+        raise forms.ValidationError("Invalid webfinger resource type")
 
     return resource_type, resource
 
 
 def clean_acct(acct_string, ensure_local=True):
     try:
-        username, hostname = acct_string.split('@')
+        username, hostname = acct_string.split("@")
     except ValueError:
-        raise forms.ValidationError('Invalid format')
+        raise forms.ValidationError("Invalid format")
 
     if ensure_local and hostname.lower() != settings.FEDERATION_HOSTNAME:
-        raise forms.ValidationError(
-            'Invalid hostname {}'.format(hostname))
+        raise forms.ValidationError("Invalid hostname {}".format(hostname))
 
     if ensure_local and username not in actors.SYSTEM_ACTORS:
-        raise forms.ValidationError('Invalid username')
+        raise forms.ValidationError("Invalid username")
 
     return username, hostname
 
@@ -45,12 +44,12 @@ def clean_acct(acct_string, ensure_local=True):
 def get_resource(resource_string):
     resource_type, resource = clean_resource(resource_string)
     username, hostname = clean_acct(resource, ensure_local=False)
-    url = 'https://{}/.well-known/webfinger?resource={}'.format(
-        hostname, resource_string)
+    url = "https://{}/.well-known/webfinger?resource={}".format(
+        hostname, resource_string
+    )
     response = session.get_session().get(
-        url,
-        verify=settings.EXTERNAL_REQUESTS_VERIFY_SSL,
-        timeout=5)
+        url, verify=settings.EXTERNAL_REQUESTS_VERIFY_SSL, timeout=5
+    )
     response.raise_for_status()
     serializer = serializers.ActorWebfingerSerializer(data=response.json())
     serializer.is_valid(raise_exception=True)
diff --git a/api/funkwhale_api/history/activities.py b/api/funkwhale_api/history/activities.py
index e478f9b7f6..3e8556ce55 100644
--- a/api/funkwhale_api/history/activities.py
+++ b/api/funkwhale_api/history/activities.py
@@ -3,17 +3,14 @@ from funkwhale_api.activity import record
 
 from . import serializers
 
-record.registry.register_serializer(
-    serializers.ListeningActivitySerializer)
+record.registry.register_serializer(serializers.ListeningActivitySerializer)
 
 
-@record.registry.register_consumer('history.Listening')
+@record.registry.register_consumer("history.Listening")
 def broadcast_listening_to_instance_activity(data, obj):
-    if obj.user.privacy_level not in ['instance', 'everyone']:
+    if obj.user.privacy_level not in ["instance", "everyone"]:
         return
 
-    channels.group_send('instance_activity', {
-        'type': 'event.send',
-        'text': '',
-        'data': data
-    })
+    channels.group_send(
+        "instance_activity", {"type": "event.send", "text": "", "data": data}
+    )
diff --git a/api/funkwhale_api/history/admin.py b/api/funkwhale_api/history/admin.py
index 5ddfb89984..cbc7f89dd4 100644
--- a/api/funkwhale_api/history/admin.py
+++ b/api/funkwhale_api/history/admin.py
@@ -2,11 +2,9 @@ from django.contrib import admin
 
 from . import models
 
+
 @admin.register(models.Listening)
 class ListeningAdmin(admin.ModelAdmin):
-    list_display = ['track', 'creation_date', 'user', 'session_key']
-    search_fields = ['track__name', 'user__username']
-    list_select_related = [
-        'user',
-        'track'
-    ]
+    list_display = ["track", "creation_date", "user", "session_key"]
+    search_fields = ["track__name", "user__username"]
+    list_select_related = ["user", "track"]
diff --git a/api/funkwhale_api/history/factories.py b/api/funkwhale_api/history/factories.py
index 86fea64d25..0524eff196 100644
--- a/api/funkwhale_api/history/factories.py
+++ b/api/funkwhale_api/history/factories.py
@@ -11,4 +11,4 @@ class ListeningFactory(factory.django.DjangoModelFactory):
     track = factory.SubFactory(factories.TrackFactory)
 
     class Meta:
-        model = 'history.Listening'
+        model = "history.Listening"
diff --git a/api/funkwhale_api/history/migrations/0001_initial.py b/api/funkwhale_api/history/migrations/0001_initial.py
index 7b6f950edf..cd27772307 100644
--- a/api/funkwhale_api/history/migrations/0001_initial.py
+++ b/api/funkwhale_api/history/migrations/0001_initial.py
@@ -9,22 +9,52 @@ import django.utils.timezone
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('music', '0008_auto_20160529_1456'),
+        ("music", "0008_auto_20160529_1456"),
         migrations.swappable_dependency(settings.AUTH_USER_MODEL),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='Listening',
+            name="Listening",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
-                ('end_date', models.DateTimeField(null=True, blank=True, default=django.utils.timezone.now)),
-                ('session_key', models.CharField(null=True, blank=True, max_length=100)),
-                ('track', models.ForeignKey(related_name='listenings', to='music.Track', on_delete=models.CASCADE)),
-                ('user', models.ForeignKey(blank=True, null=True, related_name='listenings', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
+                (
+                    "id",
+                    models.AutoField(
+                        verbose_name="ID",
+                        primary_key=True,
+                        serialize=False,
+                        auto_created=True,
+                    ),
+                ),
+                (
+                    "end_date",
+                    models.DateTimeField(
+                        null=True, blank=True, default=django.utils.timezone.now
+                    ),
+                ),
+                (
+                    "session_key",
+                    models.CharField(null=True, blank=True, max_length=100),
+                ),
+                (
+                    "track",
+                    models.ForeignKey(
+                        related_name="listenings",
+                        to="music.Track",
+                        on_delete=models.CASCADE,
+                    ),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        related_name="listenings",
+                        to=settings.AUTH_USER_MODEL,
+                        on_delete=models.CASCADE,
+                    ),
+                ),
             ],
-            options={
-                'ordering': ('-end_date',),
-            },
-        ),
+            options={"ordering": ("-end_date",)},
+        )
     ]
diff --git a/api/funkwhale_api/history/migrations/0002_auto_20180325_1433.py b/api/funkwhale_api/history/migrations/0002_auto_20180325_1433.py
index d83dbb0a46..efc0209255 100644
--- a/api/funkwhale_api/history/migrations/0002_auto_20180325_1433.py
+++ b/api/funkwhale_api/history/migrations/0002_auto_20180325_1433.py
@@ -5,18 +5,13 @@ from django.db import migrations
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('history', '0001_initial'),
-    ]
+    dependencies = [("history", "0001_initial")]
 
     operations = [
         migrations.AlterModelOptions(
-            name='listening',
-            options={'ordering': ('-creation_date',)},
+            name="listening", options={"ordering": ("-creation_date",)}
         ),
         migrations.RenameField(
-            model_name='listening',
-            old_name='end_date',
-            new_name='creation_date',
+            model_name="listening", old_name="end_date", new_name="creation_date"
         ),
     ]
diff --git a/api/funkwhale_api/history/models.py b/api/funkwhale_api/history/models.py
index 480461d35e..2edc98fbf3 100644
--- a/api/funkwhale_api/history/models.py
+++ b/api/funkwhale_api/history/models.py
@@ -6,21 +6,21 @@ from funkwhale_api.music.models import Track
 
 
 class Listening(models.Model):
-    creation_date = models.DateTimeField(
-        default=timezone.now, null=True, blank=True)
+    creation_date = models.DateTimeField(default=timezone.now, null=True, blank=True)
     track = models.ForeignKey(
-        Track, related_name="listenings", on_delete=models.CASCADE)
+        Track, related_name="listenings", on_delete=models.CASCADE
+    )
     user = models.ForeignKey(
-        'users.User',
+        "users.User",
         related_name="listenings",
         null=True,
         blank=True,
-        on_delete=models.CASCADE)
+        on_delete=models.CASCADE,
+    )
     session_key = models.CharField(max_length=100, null=True, blank=True)
 
     class Meta:
-        ordering = ('-creation_date',)
+        ordering = ("-creation_date",)
 
     def get_activity_url(self):
-        return '{}/listenings/tracks/{}'.format(
-            self.user.get_activity_url(), self.pk)
+        return "{}/listenings/tracks/{}".format(self.user.get_activity_url(), self.pk)
diff --git a/api/funkwhale_api/history/serializers.py b/api/funkwhale_api/history/serializers.py
index 572787ae00..e493227988 100644
--- a/api/funkwhale_api/history/serializers.py
+++ b/api/funkwhale_api/history/serializers.py
@@ -9,35 +9,27 @@ from . import models
 
 class ListeningActivitySerializer(activity_serializers.ModelSerializer):
     type = serializers.SerializerMethodField()
-    object = TrackActivitySerializer(source='track')
-    actor = UserActivitySerializer(source='user')
-    published = serializers.DateTimeField(source='creation_date')
+    object = TrackActivitySerializer(source="track")
+    actor = UserActivitySerializer(source="user")
+    published = serializers.DateTimeField(source="creation_date")
 
     class Meta:
         model = models.Listening
-        fields = [
-            'id',
-            'local_id',
-            'object',
-            'type',
-            'actor',
-            'published'
-        ]
+        fields = ["id", "local_id", "object", "type", "actor", "published"]
 
     def get_actor(self, obj):
         return UserActivitySerializer(obj.user).data
 
     def get_type(self, obj):
-        return 'Listen'
+        return "Listen"
 
 
 class ListeningSerializer(serializers.ModelSerializer):
-
     class Meta:
         model = models.Listening
-        fields = ('id', 'user', 'track', 'creation_date')
+        fields = ("id", "user", "track", "creation_date")
 
     def create(self, validated_data):
-        validated_data['user'] = self.context['user']
+        validated_data["user"] = self.context["user"]
 
         return super().create(validated_data)
diff --git a/api/funkwhale_api/history/urls.py b/api/funkwhale_api/history/urls.py
index 6bd72a8a24..30e0105c05 100644
--- a/api/funkwhale_api/history/urls.py
+++ b/api/funkwhale_api/history/urls.py
@@ -2,7 +2,8 @@ from django.conf.urls import include, url
 from . import views
 
 from rest_framework import routers
+
 router = routers.SimpleRouter()
-router.register(r'listenings', views.ListeningViewSet, 'listenings')
+router.register(r"listenings", views.ListeningViewSet, "listenings")
 
 urlpatterns = router.urls
diff --git a/api/funkwhale_api/history/views.py b/api/funkwhale_api/history/views.py
index 3da8b2a38b..057e6b3238 100644
--- a/api/funkwhale_api/history/views.py
+++ b/api/funkwhale_api/history/views.py
@@ -12,9 +12,8 @@ from . import serializers
 
 
 class ListeningViewSet(
-        mixins.CreateModelMixin,
-        mixins.RetrieveModelMixin,
-        viewsets.GenericViewSet):
+    mixins.CreateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
+):
 
     serializer_class = serializers.ListeningSerializer
     queryset = models.Listening.objects.all()
@@ -31,5 +30,5 @@ class ListeningViewSet(
 
     def get_serializer_context(self):
         context = super().get_serializer_context()
-        context['user'] = self.request.user
+        context["user"] = self.request.user
         return context
diff --git a/api/funkwhale_api/instance/consumers.py b/api/funkwhale_api/instance/consumers.py
index eee5f7f0e3..bb213a0013 100644
--- a/api/funkwhale_api/instance/consumers.py
+++ b/api/funkwhale_api/instance/consumers.py
@@ -5,4 +5,4 @@ class InstanceActivityConsumer(JsonAuthConsumer):
     groups = ["instance_activity"]
 
     def event_send(self, message):
-        self.send_json(message['data'])
+        self.send_json(message["data"])
diff --git a/api/funkwhale_api/instance/dynamic_preferences_registry.py b/api/funkwhale_api/instance/dynamic_preferences_registry.py
index 8ccf80dd93..618bd8ea4d 100644
--- a/api/funkwhale_api/instance/dynamic_preferences_registry.py
+++ b/api/funkwhale_api/instance/dynamic_preferences_registry.py
@@ -3,91 +3,83 @@ from django.forms import widgets
 from dynamic_preferences import types
 from dynamic_preferences.registries import global_preferences_registry
 
-raven = types.Section('raven')
-instance = types.Section('instance')
+raven = types.Section("raven")
+instance = types.Section("instance")
 
 
 @global_preferences_registry.register
 class InstanceName(types.StringPreference):
     show_in_api = True
     section = instance
-    name = 'name'
-    default = ''
-    verbose_name = 'Public name'
-    help_text = 'The public name of your instance, displayed in the about page.'
-    field_kwargs = {
-        'required': False,
-    }
+    name = "name"
+    default = ""
+    verbose_name = "Public name"
+    help_text = "The public name of your instance, displayed in the about page."
+    field_kwargs = {"required": False}
 
 
 @global_preferences_registry.register
 class InstanceShortDescription(types.StringPreference):
     show_in_api = True
     section = instance
-    name = 'short_description'
-    default = ''
-    verbose_name = 'Short description'
-    help_text = 'Instance succinct description, displayed in the about page.'
-    field_kwargs = {
-        'required': False,
-    }
+    name = "short_description"
+    default = ""
+    verbose_name = "Short description"
+    help_text = "Instance succinct description, displayed in the about page."
+    field_kwargs = {"required": False}
 
 
 @global_preferences_registry.register
 class InstanceLongDescription(types.StringPreference):
     show_in_api = True
     section = instance
-    name = 'long_description'
-    verbose_name = 'Long description'
-    default = ''
-    help_text = 'Instance long description, displayed in the about page (markdown allowed).'
+    name = "long_description"
+    verbose_name = "Long description"
+    default = ""
+    help_text = (
+        "Instance long description, displayed in the about page (markdown allowed)."
+    )
     widget = widgets.Textarea
-    field_kwargs = {
-        'required': False,
-    }
+    field_kwargs = {"required": False}
 
 
 @global_preferences_registry.register
 class RavenDSN(types.StringPreference):
     show_in_api = True
     section = raven
-    name = 'front_dsn'
-    default = 'https://9e0562d46b09442bb8f6844e50cbca2b@sentry.eliotberriot.com/4'
-    verbose_name = 'Raven DSN key (front-end)'
+    name = "front_dsn"
+    default = "https://9e0562d46b09442bb8f6844e50cbca2b@sentry.eliotberriot.com/4"
+    verbose_name = "Raven DSN key (front-end)"
 
     help_text = (
-        'A Raven DSN key used to report front-ent errors to '
-        'a sentry instance. Keeping the default one will report errors to '
-        'Funkwhale developers.'
+        "A Raven DSN key used to report front-ent errors to "
+        "a sentry instance. Keeping the default one will report errors to "
+        "Funkwhale developers."
     )
-    field_kwargs = {
-        'required': False,
-    }
+    field_kwargs = {"required": False}
 
 
 @global_preferences_registry.register
 class RavenEnabled(types.BooleanPreference):
     show_in_api = True
     section = raven
-    name = 'front_enabled'
+    name = "front_enabled"
     default = False
-    verbose_name = (
-        'Report front-end errors with Raven'
-    )
+    verbose_name = "Report front-end errors with Raven"
 
 
 @global_preferences_registry.register
 class InstanceNodeinfoEnabled(types.BooleanPreference):
     show_in_api = False
     section = instance
-    name = 'nodeinfo_enabled'
+    name = "nodeinfo_enabled"
     default = True
-    verbose_name = 'Enable nodeinfo endpoint'
+    verbose_name = "Enable nodeinfo endpoint"
     help_text = (
-        'This endpoint is needed for your about page to work. '
-        'It\'s also helpful for the various monitoring '
-        'tools that map and analyzize the fediverse, '
-        'but you can disable it completely if needed.'
+        "This endpoint is needed for your about page to work. "
+        "It's also helpful for the various monitoring "
+        "tools that map and analyzize the fediverse, "
+        "but you can disable it completely if needed."
     )
 
 
@@ -95,13 +87,13 @@ class InstanceNodeinfoEnabled(types.BooleanPreference):
 class InstanceNodeinfoPrivate(types.BooleanPreference):
     show_in_api = False
     section = instance
-    name = 'nodeinfo_private'
+    name = "nodeinfo_private"
     default = False
-    verbose_name = 'Private mode in nodeinfo'
+    verbose_name = "Private mode in nodeinfo"
     help_text = (
-        'Indicate in the nodeinfo endpoint that you do not want your instance '
-        'to be tracked by third-party services. '
-        'There is no guarantee these tools will honor this setting though.'
+        "Indicate in the nodeinfo endpoint that you do not want your instance "
+        "to be tracked by third-party services. "
+        "There is no guarantee these tools will honor this setting though."
     )
 
 
@@ -109,10 +101,10 @@ class InstanceNodeinfoPrivate(types.BooleanPreference):
 class InstanceNodeinfoStatsEnabled(types.BooleanPreference):
     show_in_api = False
     section = instance
-    name = 'nodeinfo_stats_enabled'
+    name = "nodeinfo_stats_enabled"
     default = True
-    verbose_name = 'Enable usage and library stats in nodeinfo endpoint'
+    verbose_name = "Enable usage and library stats in nodeinfo endpoint"
     help_text = (
-        'Disable this if you don\'t want to share usage and library statistics '
-        'in the nodeinfo endpoint but don\'t want to disable it completely.'
+        "Disable this if you don't want to share usage and library statistics "
+        "in the nodeinfo endpoint but don't want to disable it completely."
     )
diff --git a/api/funkwhale_api/instance/nodeinfo.py b/api/funkwhale_api/instance/nodeinfo.py
index dbc005af7b..ea982a63fe 100644
--- a/api/funkwhale_api/instance/nodeinfo.py
+++ b/api/funkwhale_api/instance/nodeinfo.py
@@ -6,70 +6,47 @@ from funkwhale_api.common import preferences
 from . import stats
 
 
-store = memoize.djangocache.Cache('default')
-memo = memoize.Memoizer(store, namespace='instance:stats')
+store = memoize.djangocache.Cache("default")
+memo = memoize.Memoizer(store, namespace="instance:stats")
 
 
 def get():
-    share_stats = preferences.get('instance__nodeinfo_stats_enabled')
-    private = preferences.get('instance__nodeinfo_private')
+    share_stats = preferences.get("instance__nodeinfo_stats_enabled")
+    private = preferences.get("instance__nodeinfo_private")
     data = {
-        'version': '2.0',
-        'software': {
-            'name': 'funkwhale',
-            'version': funkwhale_api.__version__
-        },
-        'protocols': ['activitypub'],
-        'services': {
-            'inbound': [],
-            'outbound': []
-        },
-        'openRegistrations': preferences.get('users__registration_enabled'),
-        'usage': {
-            'users': {
-                'total': 0,
-            }
-        },
-        'metadata': {
-            'private': preferences.get('instance__nodeinfo_private'),
-            'shortDescription': preferences.get('instance__short_description'),
-            'longDescription': preferences.get('instance__long_description'),
-            'nodeName': preferences.get('instance__name'),
-            'library': {
-                'federationEnabled': preferences.get('federation__enabled'),
-                'federationNeedsApproval': preferences.get('federation__music_needs_approval'),
-                'anonymousCanListen': preferences.get('common__api_authentication_required'),
+        "version": "2.0",
+        "software": {"name": "funkwhale", "version": funkwhale_api.__version__},
+        "protocols": ["activitypub"],
+        "services": {"inbound": [], "outbound": []},
+        "openRegistrations": preferences.get("users__registration_enabled"),
+        "usage": {"users": {"total": 0}},
+        "metadata": {
+            "private": preferences.get("instance__nodeinfo_private"),
+            "shortDescription": preferences.get("instance__short_description"),
+            "longDescription": preferences.get("instance__long_description"),
+            "nodeName": preferences.get("instance__name"),
+            "library": {
+                "federationEnabled": preferences.get("federation__enabled"),
+                "federationNeedsApproval": preferences.get(
+                    "federation__music_needs_approval"
+                ),
+                "anonymousCanListen": preferences.get(
+                    "common__api_authentication_required"
+                ),
             },
-        }
+        },
     }
     if share_stats:
-        getter = memo(
-            lambda: stats.get(),
-            max_age=600
-        )
+        getter = memo(lambda: stats.get(), max_age=600)
         statistics = getter()
-        data['usage']['users']['total'] = statistics['users']
-        data['metadata']['library']['tracks'] = {
-            'total': statistics['tracks'],
-        }
-        data['metadata']['library']['artists'] = {
-            'total': statistics['artists'],
-        }
-        data['metadata']['library']['albums'] = {
-            'total': statistics['albums'],
-        }
-        data['metadata']['library']['music'] = {
-            'hours': statistics['music_duration']
-        }
+        data["usage"]["users"]["total"] = statistics["users"]
+        data["metadata"]["library"]["tracks"] = {"total": statistics["tracks"]}
+        data["metadata"]["library"]["artists"] = {"total": statistics["artists"]}
+        data["metadata"]["library"]["albums"] = {"total": statistics["albums"]}
+        data["metadata"]["library"]["music"] = {"hours": statistics["music_duration"]}
 
-        data['metadata']['usage'] = {
-            'favorites': {
-                'tracks': {
-                    'total': statistics['track_favorites'],
-                }
-            },
-            'listenings': {
-                'total': statistics['listenings']
-            }
+        data["metadata"]["usage"] = {
+            "favorites": {"tracks": {"total": statistics["track_favorites"]}},
+            "listenings": {"total": statistics["listenings"]},
         }
     return data
diff --git a/api/funkwhale_api/instance/stats.py b/api/funkwhale_api/instance/stats.py
index 167b333d6d..061aade750 100644
--- a/api/funkwhale_api/instance/stats.py
+++ b/api/funkwhale_api/instance/stats.py
@@ -8,13 +8,13 @@ from funkwhale_api.users.models import User
 
 def get():
     return {
-        'users': get_users(),
-        'tracks': get_tracks(),
-        'albums': get_albums(),
-        'artists': get_artists(),
-        'track_favorites': get_track_favorites(),
-        'listenings': get_listenings(),
-        'music_duration': get_music_duration(),
+        "users": get_users(),
+        "tracks": get_tracks(),
+        "albums": get_albums(),
+        "artists": get_artists(),
+        "track_favorites": get_track_favorites(),
+        "listenings": get_listenings(),
+        "music_duration": get_music_duration(),
     }
 
 
@@ -43,9 +43,7 @@ def get_artists():
 
 
 def get_music_duration():
-    seconds = models.TrackFile.objects.aggregate(
-        d=Sum('duration'),
-    )['d']
+    seconds = models.TrackFile.objects.aggregate(d=Sum("duration"))["d"]
     if seconds:
         return seconds / 3600
     return 0
diff --git a/api/funkwhale_api/instance/urls.py b/api/funkwhale_api/instance/urls.py
index 7992842c03..05682b1e76 100644
--- a/api/funkwhale_api/instance/urls.py
+++ b/api/funkwhale_api/instance/urls.py
@@ -2,10 +2,11 @@ from django.conf.urls import url
 from rest_framework import routers
 
 from . import views
+
 admin_router = routers.SimpleRouter()
-admin_router.register(r'admin/settings', views.AdminSettings, 'admin-settings')
+admin_router.register(r"admin/settings", views.AdminSettings, "admin-settings")
 
 urlpatterns = [
-    url(r'^nodeinfo/2.0/$', views.NodeInfo.as_view(), name='nodeinfo-2.0'),
-    url(r'^settings/$', views.InstanceSettings.as_view(), name='settings'),
+    url(r"^nodeinfo/2.0/$", views.NodeInfo.as_view(), name="nodeinfo-2.0"),
+    url(r"^settings/$", views.InstanceSettings.as_view(), name="settings"),
 ] + admin_router.urls
diff --git a/api/funkwhale_api/instance/views.py b/api/funkwhale_api/instance/views.py
index b905acd3e6..69663c1256 100644
--- a/api/funkwhale_api/instance/views.py
+++ b/api/funkwhale_api/instance/views.py
@@ -12,15 +12,14 @@ from . import nodeinfo
 from . import stats
 
 
-NODEINFO_2_CONTENT_TYPE = (
-    'application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8'  # noqa
-)
+NODEINFO_2_CONTENT_TYPE = "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8"  # noqa
 
 
 class AdminSettings(preferences_viewsets.GlobalPreferencesViewSet):
     pagination_class = None
     permission_classes = (HasUserPermission,)
-    required_permissions = ['settings']
+    required_permissions = ["settings"]
+
 
 class InstanceSettings(views.APIView):
     permission_classes = []
@@ -29,16 +28,11 @@ class InstanceSettings(views.APIView):
     def get(self, request, *args, **kwargs):
         manager = global_preferences_registry.manager()
         manager.all()
-        all_preferences = manager.model.objects.all().order_by(
-            'section', 'name'
-        )
+        all_preferences = manager.model.objects.all().order_by("section", "name")
         api_preferences = [
-            p
-            for p in all_preferences
-            if getattr(p.preference, 'show_in_api', False)
+            p for p in all_preferences if getattr(p.preference, "show_in_api", False)
         ]
-        data = serializers.GlobalPreferenceSerializer(
-            api_preferences, many=True).data
+        data = serializers.GlobalPreferenceSerializer(api_preferences, many=True).data
         return Response(data, status=200)
 
 
@@ -47,8 +41,7 @@ class NodeInfo(views.APIView):
     authentication_classes = []
 
     def get(self, request, *args, **kwargs):
-        if not preferences.get('instance__nodeinfo_enabled'):
+        if not preferences.get("instance__nodeinfo_enabled"):
             return Response(status=404)
         data = nodeinfo.get()
-        return Response(
-            data, status=200, content_type=NODEINFO_2_CONTENT_TYPE)
+        return Response(data, status=200, content_type=NODEINFO_2_CONTENT_TYPE)
diff --git a/api/funkwhale_api/manage/filters.py b/api/funkwhale_api/manage/filters.py
index 9853b7a61f..6bb8a1ccc2 100644
--- a/api/funkwhale_api/manage/filters.py
+++ b/api/funkwhale_api/manage/filters.py
@@ -7,19 +7,15 @@ from funkwhale_api.music import models as music_models
 
 
 class ManageTrackFileFilterSet(filters.FilterSet):
-    q = fields.SearchFilter(search_fields=[
-        'track__title',
-        'track__album__title',
-        'track__artist__name',
-        'source',
-    ])
+    q = fields.SearchFilter(
+        search_fields=[
+            "track__title",
+            "track__album__title",
+            "track__artist__name",
+            "source",
+        ]
+    )
 
     class Meta:
         model = music_models.TrackFile
-        fields = [
-            'q',
-            'track__album',
-            'track__artist',
-            'track',
-            'library_track'
-        ]
+        fields = ["q", "track__album", "track__artist", "track", "library_track"]
diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py
index 02300ec068..1c94cf5538 100644
--- a/api/funkwhale_api/manage/serializers.py
+++ b/api/funkwhale_api/manage/serializers.py
@@ -10,12 +10,7 @@ from . import filters
 class ManageTrackFileArtistSerializer(serializers.ModelSerializer):
     class Meta:
         model = music_models.Artist
-        fields = [
-            'id',
-            'mbid',
-            'creation_date',
-            'name',
-        ]
+        fields = ["id", "mbid", "creation_date", "name"]
 
 
 class ManageTrackFileAlbumSerializer(serializers.ModelSerializer):
@@ -24,13 +19,13 @@ class ManageTrackFileAlbumSerializer(serializers.ModelSerializer):
     class Meta:
         model = music_models.Album
         fields = (
-            'id',
-            'mbid',
-            'title',
-            'artist',
-            'release_date',
-            'cover',
-            'creation_date',
+            "id",
+            "mbid",
+            "title",
+            "artist",
+            "release_date",
+            "cover",
+            "creation_date",
         )
 
 
@@ -40,15 +35,7 @@ class ManageTrackFileTrackSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = music_models.Track
-        fields = (
-            'id',
-            'mbid',
-            'title',
-            'album',
-            'artist',
-            'creation_date',
-            'position',
-        )
+        fields = ("id", "mbid", "title", "album", "artist", "creation_date", "position")
 
 
 class ManageTrackFileSerializer(serializers.ModelSerializer):
@@ -57,24 +44,24 @@ class ManageTrackFileSerializer(serializers.ModelSerializer):
     class Meta:
         model = music_models.TrackFile
         fields = (
-            'id',
-            'path',
-            'source',
-            'filename',
-            'mimetype',
-            'track',
-            'duration',
-            'mimetype',
-            'bitrate',
-            'size',
-            'path',
-            'library_track',
+            "id",
+            "path",
+            "source",
+            "filename",
+            "mimetype",
+            "track",
+            "duration",
+            "mimetype",
+            "bitrate",
+            "size",
+            "path",
+            "library_track",
         )
 
 
 class ManageTrackFileActionSerializer(common_serializers.ActionSerializer):
-    actions = ['delete']
-    dangerous_actions = ['delete']
+    actions = ["delete"]
+    dangerous_actions = ["delete"]
     filterset_class = filters.ManageTrackFileFilterSet
 
     @transaction.atomic
diff --git a/api/funkwhale_api/manage/urls.py b/api/funkwhale_api/manage/urls.py
index c434581ecd..38ed7d1c99 100644
--- a/api/funkwhale_api/manage/urls.py
+++ b/api/funkwhale_api/manage/urls.py
@@ -2,10 +2,10 @@ from django.conf.urls import include, url
 from . import views
 
 from rest_framework import routers
+
 library_router = routers.SimpleRouter()
-library_router.register(r'track-files', views.ManageTrackFileViewSet, 'track-files')
+library_router.register(r"track-files", views.ManageTrackFileViewSet, "track-files")
 
 urlpatterns = [
-    url(r'^library/',
-        include((library_router.urls, 'instance'), namespace='library')),
+    url(r"^library/", include((library_router.urls, "instance"), namespace="library"))
 ]
diff --git a/api/funkwhale_api/manage/views.py b/api/funkwhale_api/manage/views.py
index 74059caa1d..77581acf0e 100644
--- a/api/funkwhale_api/manage/views.py
+++ b/api/funkwhale_api/manage/views.py
@@ -11,38 +11,35 @@ from . import serializers
 
 
 class ManageTrackFileViewSet(
-        mixins.ListModelMixin,
-        mixins.RetrieveModelMixin,
-        mixins.DestroyModelMixin,
-        viewsets.GenericViewSet):
+    mixins.ListModelMixin,
+    mixins.RetrieveModelMixin,
+    mixins.DestroyModelMixin,
+    viewsets.GenericViewSet,
+):
     queryset = (
         music_models.TrackFile.objects.all()
-            .select_related(
-                'track__artist',
-                'track__album__artist',
-                'library_track')
-            .order_by('-id')
+        .select_related("track__artist", "track__album__artist", "library_track")
+        .order_by("-id")
     )
     serializer_class = serializers.ManageTrackFileSerializer
     filter_class = filters.ManageTrackFileFilterSet
     permission_classes = (HasUserPermission,)
-    required_permissions = ['library']
+    required_permissions = ["library"]
     ordering_fields = [
-        'accessed_date',
-        'modification_date',
-        'creation_date',
-        'track__artist__name',
-        'bitrate',
-        'size',
-        'duration',
+        "accessed_date",
+        "modification_date",
+        "creation_date",
+        "track__artist__name",
+        "bitrate",
+        "size",
+        "duration",
     ]
 
-    @list_route(methods=['post'])
+    @list_route(methods=["post"])
     def action(self, request, *args, **kwargs):
         queryset = self.get_queryset()
         serializer = serializers.ManageTrackFileActionSerializer(
-            request.data,
-            queryset=queryset,
+            request.data, queryset=queryset
         )
         serializer.is_valid(raise_exception=True)
         result = serializer.save()
diff --git a/api/funkwhale_api/music/admin.py b/api/funkwhale_api/music/admin.py
index 1654428baf..a5775acd68 100644
--- a/api/funkwhale_api/music/admin.py
+++ b/api/funkwhale_api/music/admin.py
@@ -5,85 +5,73 @@ from . import models
 
 @admin.register(models.Artist)
 class ArtistAdmin(admin.ModelAdmin):
-    list_display = ['name', 'mbid', 'creation_date']
-    search_fields = ['name', 'mbid']
+    list_display = ["name", "mbid", "creation_date"]
+    search_fields = ["name", "mbid"]
 
 
 @admin.register(models.Album)
 class AlbumAdmin(admin.ModelAdmin):
-    list_display = ['title', 'artist', 'mbid', 'release_date', 'creation_date']
-    search_fields = ['title', 'artist__name', 'mbid']
+    list_display = ["title", "artist", "mbid", "release_date", "creation_date"]
+    search_fields = ["title", "artist__name", "mbid"]
     list_select_related = True
 
 
 @admin.register(models.Track)
 class TrackAdmin(admin.ModelAdmin):
-    list_display = ['title', 'artist', 'album', 'mbid']
-    search_fields = ['title', 'artist__name', 'album__title', 'mbid']
+    list_display = ["title", "artist", "album", "mbid"]
+    search_fields = ["title", "artist__name", "album__title", "mbid"]
     list_select_related = True
 
 
 @admin.register(models.ImportBatch)
 class ImportBatchAdmin(admin.ModelAdmin):
-    list_display = [
-        'submitted_by',
-        'creation_date',
-        'import_request',
-        'status']
-    list_select_related = [
-        'submitted_by',
-        'import_request',
-    ]
-    list_filter = ['status']
-    search_fields = [
-        'import_request__name', 'source', 'batch__pk', 'mbid']
+    list_display = ["submitted_by", "creation_date", "import_request", "status"]
+    list_select_related = ["submitted_by", "import_request"]
+    list_filter = ["status"]
+    search_fields = ["import_request__name", "source", "batch__pk", "mbid"]
 
 
 @admin.register(models.ImportJob)
 class ImportJobAdmin(admin.ModelAdmin):
-    list_display = ['source', 'batch', 'track_file', 'status', 'mbid']
-    list_select_related = [
-        'track_file',
-        'batch',
-    ]
-    search_fields = ['source', 'batch__pk', 'mbid']
-    list_filter = ['status']
+    list_display = ["source", "batch", "track_file", "status", "mbid"]
+    list_select_related = ["track_file", "batch"]
+    search_fields = ["source", "batch__pk", "mbid"]
+    list_filter = ["status"]
 
 
 @admin.register(models.Work)
 class WorkAdmin(admin.ModelAdmin):
-    list_display = ['title', 'mbid', 'language', 'nature']
+    list_display = ["title", "mbid", "language", "nature"]
     list_select_related = True
-    search_fields = ['title']
-    list_filter = ['language', 'nature']
+    search_fields = ["title"]
+    list_filter = ["language", "nature"]
 
 
 @admin.register(models.Lyrics)
 class LyricsAdmin(admin.ModelAdmin):
-    list_display = ['url', 'id', 'url']
+    list_display = ["url", "id", "url"]
     list_select_related = True
-    search_fields = ['url', 'work__title']
-    list_filter = ['work__language']
+    search_fields = ["url", "work__title"]
+    list_filter = ["work__language"]
 
 
 @admin.register(models.TrackFile)
 class TrackFileAdmin(admin.ModelAdmin):
     list_display = [
-        'track',
-        'audio_file',
-        'source',
-        'duration',
-        'mimetype',
-        'size',
-        'bitrate'
-    ]
-    list_select_related = [
-        'track'
+        "track",
+        "audio_file",
+        "source",
+        "duration",
+        "mimetype",
+        "size",
+        "bitrate",
     ]
+    list_select_related = ["track"]
     search_fields = [
-        'source',
-        'acoustid_track_id',
-        'track__title',
-        'track__album__title',
-        'track__artist__name']
-    list_filter = ['mimetype']
+        "source",
+        "acoustid_track_id",
+        "track__title",
+        "track__album__title",
+        "track__artist__name",
+    ]
+    list_filter = ["mimetype"]
diff --git a/api/funkwhale_api/music/factories.py b/api/funkwhale_api/music/factories.py
index 11423f5b01..6e2e6a1155 100644
--- a/api/funkwhale_api/music/factories.py
+++ b/api/funkwhale_api/music/factories.py
@@ -2,78 +2,72 @@ import factory
 import os
 
 from funkwhale_api.factories import registry, ManyToManyFromList
-from funkwhale_api.federation.factories import (
-    LibraryTrackFactory,
-)
+from funkwhale_api.federation.factories import LibraryTrackFactory
 from funkwhale_api.users.factories import UserFactory
 
 SAMPLES_PATH = os.path.join(
     os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
-    'tests', 'music'
+    "tests",
+    "music",
 )
 
 
 @registry.register
 class ArtistFactory(factory.django.DjangoModelFactory):
-    name = factory.Faker('name')
-    mbid = factory.Faker('uuid4')
+    name = factory.Faker("name")
+    mbid = factory.Faker("uuid4")
 
     class Meta:
-        model = 'music.Artist'
+        model = "music.Artist"
 
 
 @registry.register
 class AlbumFactory(factory.django.DjangoModelFactory):
-    title = factory.Faker('sentence', nb_words=3)
-    mbid = factory.Faker('uuid4')
-    release_date = factory.Faker('date_object')
+    title = factory.Faker("sentence", nb_words=3)
+    mbid = factory.Faker("uuid4")
+    release_date = factory.Faker("date_object")
     cover = factory.django.ImageField()
     artist = factory.SubFactory(ArtistFactory)
-    release_group_id = factory.Faker('uuid4')
+    release_group_id = factory.Faker("uuid4")
 
     class Meta:
-        model = 'music.Album'
+        model = "music.Album"
 
 
 @registry.register
 class TrackFactory(factory.django.DjangoModelFactory):
-    title = factory.Faker('sentence', nb_words=3)
-    mbid = factory.Faker('uuid4')
+    title = factory.Faker("sentence", nb_words=3)
+    mbid = factory.Faker("uuid4")
     album = factory.SubFactory(AlbumFactory)
-    artist = factory.SelfAttribute('album.artist')
+    artist = factory.SelfAttribute("album.artist")
     position = 1
-    tags = ManyToManyFromList('tags')
+    tags = ManyToManyFromList("tags")
 
     class Meta:
-        model = 'music.Track'
+        model = "music.Track"
 
 
 @registry.register
 class TrackFileFactory(factory.django.DjangoModelFactory):
     track = factory.SubFactory(TrackFactory)
     audio_file = factory.django.FileField(
-        from_path=os.path.join(SAMPLES_PATH, 'test.ogg'))
+        from_path=os.path.join(SAMPLES_PATH, "test.ogg")
+    )
 
     bitrate = None
     size = None
     duration = None
 
     class Meta:
-        model = 'music.TrackFile'
+        model = "music.TrackFile"
 
     class Params:
-        in_place = factory.Trait(
-            audio_file=None,
-        )
+        in_place = factory.Trait(audio_file=None)
         federation = factory.Trait(
             audio_file=None,
             library_track=factory.SubFactory(LibraryTrackFactory),
-            mimetype=factory.LazyAttribute(
-                lambda o: o.library_track.audio_mimetype
-            ),
-            source=factory.LazyAttribute(
-                lambda o: o.library_track.audio_url
-            ),
+            mimetype=factory.LazyAttribute(lambda o: o.library_track.audio_mimetype),
+            source=factory.LazyAttribute(lambda o: o.library_track.audio_url),
         )
 
 
@@ -82,26 +76,21 @@ class ImportBatchFactory(factory.django.DjangoModelFactory):
     submitted_by = factory.SubFactory(UserFactory)
 
     class Meta:
-        model = 'music.ImportBatch'
+        model = "music.ImportBatch"
 
     class Params:
-        federation = factory.Trait(
-            submitted_by=None,
-            source='federation',
-        )
-        finished = factory.Trait(
-            status='finished',
-        )
+        federation = factory.Trait(submitted_by=None, source="federation")
+        finished = factory.Trait(status="finished")
 
 
 @registry.register
 class ImportJobFactory(factory.django.DjangoModelFactory):
     batch = factory.SubFactory(ImportBatchFactory)
-    source = factory.Faker('url')
-    mbid = factory.Faker('uuid4')
+    source = factory.Faker("url")
+    mbid = factory.Faker("uuid4")
 
     class Meta:
-        model = 'music.ImportJob'
+        model = "music.ImportJob"
 
     class Params:
         federation = factory.Trait(
@@ -110,53 +99,51 @@ class ImportJobFactory(factory.django.DjangoModelFactory):
             batch=factory.SubFactory(ImportBatchFactory, federation=True),
         )
         finished = factory.Trait(
-            status='finished',
-            track_file=factory.SubFactory(TrackFileFactory),
-        )
-        in_place = factory.Trait(
-            status='finished',
-            audio_file=None,
+            status="finished", track_file=factory.SubFactory(TrackFileFactory)
         )
+        in_place = factory.Trait(status="finished", audio_file=None)
         with_audio_file = factory.Trait(
-            status='finished',
+            status="finished",
             audio_file=factory.django.FileField(
-                from_path=os.path.join(SAMPLES_PATH, 'test.ogg')),
+                from_path=os.path.join(SAMPLES_PATH, "test.ogg")
+            ),
         )
 
 
-@registry.register(name='music.FileImportJob')
+@registry.register(name="music.FileImportJob")
 class FileImportJobFactory(ImportJobFactory):
-    source = 'file://'
+    source = "file://"
     mbid = None
     audio_file = factory.django.FileField(
-        from_path=os.path.join(SAMPLES_PATH, 'test.ogg'))
+        from_path=os.path.join(SAMPLES_PATH, "test.ogg")
+    )
 
 
 @registry.register
 class WorkFactory(factory.django.DjangoModelFactory):
-    mbid = factory.Faker('uuid4')
-    language = 'eng'
-    nature = 'song'
-    title = factory.Faker('sentence', nb_words=3)
+    mbid = factory.Faker("uuid4")
+    language = "eng"
+    nature = "song"
+    title = factory.Faker("sentence", nb_words=3)
 
     class Meta:
-        model = 'music.Work'
+        model = "music.Work"
 
 
 @registry.register
 class LyricsFactory(factory.django.DjangoModelFactory):
     work = factory.SubFactory(WorkFactory)
-    url = factory.Faker('url')
-    content = factory.Faker('paragraphs', nb=4)
+    url = factory.Faker("url")
+    content = factory.Faker("paragraphs", nb=4)
 
     class Meta:
-        model = 'music.Lyrics'
+        model = "music.Lyrics"
 
 
 @registry.register
 class TagFactory(factory.django.DjangoModelFactory):
-    name = factory.SelfAttribute('slug')
-    slug = factory.Faker('slug')
+    name = factory.SelfAttribute("slug")
+    slug = factory.Faker("slug")
 
     class Meta:
-        model = 'taggit.Tag'
+        model = "taggit.Tag"
diff --git a/api/funkwhale_api/music/fake_data.py b/api/funkwhale_api/music/fake_data.py
index 892b784cae..de7b61ef54 100644
--- a/api/funkwhale_api/music/fake_data.py
+++ b/api/funkwhale_api/music/fake_data.py
@@ -10,13 +10,15 @@ from funkwhale_api.music import factories
 def create_data(count=25):
     artists = factories.ArtistFactory.create_batch(size=count)
     for artist in artists:
-        print('Creating data for', artist)
+        print("Creating data for", artist)
         albums = factories.AlbumFactory.create_batch(
-            artist=artist, size=random.randint(1, 5))
+            artist=artist, size=random.randint(1, 5)
+        )
         for album in albums:
             factories.TrackFileFactory.create_batch(
-                track__album=album, size=random.randint(3, 18))
+                track__album=album, size=random.randint(3, 18)
+            )
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     create_data()
diff --git a/api/funkwhale_api/music/filters.py b/api/funkwhale_api/music/filters.py
index dc7aafc219..6e1e4df09d 100644
--- a/api/funkwhale_api/music/filters.py
+++ b/api/funkwhale_api/music/filters.py
@@ -7,12 +7,10 @@ from . import models
 
 
 class ListenableMixin(filters.FilterSet):
-    listenable = filters.BooleanFilter(name='_', method='filter_listenable')
+    listenable = filters.BooleanFilter(name="_", method="filter_listenable")
 
     def filter_listenable(self, queryset, name, value):
-        queryset = queryset.annotate(
-            files_count=Count('tracks__files')
-        )
+        queryset = queryset.annotate(files_count=Count("tracks__files"))
         if value:
             return queryset.filter(files_count__gt=0)
         else:
@@ -20,39 +18,31 @@ class ListenableMixin(filters.FilterSet):
 
 
 class ArtistFilter(ListenableMixin):
-    q = fields.SearchFilter(search_fields=[
-        'name',
-    ])
+    q = fields.SearchFilter(search_fields=["name"])
 
     class Meta:
         model = models.Artist
         fields = {
-            'name': ['exact', 'iexact', 'startswith', 'icontains'],
-            'listenable': 'exact',
+            "name": ["exact", "iexact", "startswith", "icontains"],
+            "listenable": "exact",
         }
 
 
 class TrackFilter(filters.FilterSet):
-    q = fields.SearchFilter(search_fields=[
-        'title',
-        'album__title',
-        'artist__name',
-    ])
-    listenable = filters.BooleanFilter(name='_', method='filter_listenable')
+    q = fields.SearchFilter(search_fields=["title", "album__title", "artist__name"])
+    listenable = filters.BooleanFilter(name="_", method="filter_listenable")
 
     class Meta:
         model = models.Track
         fields = {
-            'title': ['exact', 'iexact', 'startswith', 'icontains'],
-            'listenable': ['exact'],
-            'artist': ['exact'],
-            'album': ['exact'],
+            "title": ["exact", "iexact", "startswith", "icontains"],
+            "listenable": ["exact"],
+            "artist": ["exact"],
+            "album": ["exact"],
         }
 
     def filter_listenable(self, queryset, name, value):
-        queryset = queryset.annotate(
-            files_count=Count('files')
-        )
+        queryset = queryset.annotate(files_count=Count("files"))
         if value:
             return queryset.filter(files_count__gt=0)
         else:
@@ -60,46 +50,32 @@ class TrackFilter(filters.FilterSet):
 
 
 class ImportBatchFilter(filters.FilterSet):
-    q = fields.SearchFilter(search_fields=[
-        'submitted_by__username',
-        'source',
-    ])
+    q = fields.SearchFilter(search_fields=["submitted_by__username", "source"])
 
     class Meta:
         model = models.ImportBatch
-        fields = {
-            'status': ['exact'],
-            'source': ['exact'],
-            'submitted_by': ['exact'],
-        }
+        fields = {"status": ["exact"], "source": ["exact"], "submitted_by": ["exact"]}
 
 
 class ImportJobFilter(filters.FilterSet):
-    q = fields.SearchFilter(search_fields=[
-        'batch__submitted_by__username',
-        'source',
-    ])
+    q = fields.SearchFilter(search_fields=["batch__submitted_by__username", "source"])
 
     class Meta:
         model = models.ImportJob
         fields = {
-            'batch': ['exact'],
-            'batch__status': ['exact'],
-            'batch__source': ['exact'],
-            'batch__submitted_by': ['exact'],
-            'status': ['exact'],
-            'source': ['exact'],
+            "batch": ["exact"],
+            "batch__status": ["exact"],
+            "batch__source": ["exact"],
+            "batch__submitted_by": ["exact"],
+            "status": ["exact"],
+            "source": ["exact"],
         }
 
 
 class AlbumFilter(ListenableMixin):
-    listenable = filters.BooleanFilter(name='_', method='filter_listenable')
-    q = fields.SearchFilter(search_fields=[
-        'title',
-        'artist__name'
-        'source',
-    ])
+    listenable = filters.BooleanFilter(name="_", method="filter_listenable")
+    q = fields.SearchFilter(search_fields=["title", "artist__name" "source"])
 
     class Meta:
         model = models.Album
-        fields = ['listenable', 'q', 'artist']
+        fields = ["listenable", "q", "artist"]
diff --git a/api/funkwhale_api/music/importers.py b/api/funkwhale_api/music/importers.py
index 7e26fe9689..ce7ded02b4 100644
--- a/api/funkwhale_api/music/importers.py
+++ b/api/funkwhale_api/music/importers.py
@@ -1,42 +1,43 @@
-
-
 def load(model, *args, **kwargs):
     importer = registry[model.__name__](model=model)
     return importer.load(*args, **kwargs)
 
+
 class Importer(object):
     def __init__(self, model):
         self.model = model
 
     def load(self, cleaned_data, raw_data, import_hooks):
-        mbid = cleaned_data.pop('mbid')
+        mbid = cleaned_data.pop("mbid")
         m = self.model.objects.update_or_create(mbid=mbid, defaults=cleaned_data)[0]
         for hook in import_hooks:
             hook(m, cleaned_data, raw_data)
         return m
 
+
 class Mapping(object):
     """Cast musicbrainz data to funkwhale data and vice-versa"""
+
     def __init__(self, musicbrainz_mapping):
         self.musicbrainz_mapping = musicbrainz_mapping
 
         self._from_musicbrainz = {}
         self._to_musicbrainz = {}
         for field_name, conf in self.musicbrainz_mapping.items():
-            self._from_musicbrainz[conf['musicbrainz_field_name']] = {
-                'field_name': field_name,
-                'converter': conf.get('converter', lambda v: v)
+            self._from_musicbrainz[conf["musicbrainz_field_name"]] = {
+                "field_name": field_name,
+                "converter": conf.get("converter", lambda v: v),
             }
             self._to_musicbrainz[field_name] = {
-                'field_name': conf['musicbrainz_field_name'],
-                'converter': conf.get('converter', lambda v: v)
+                "field_name": conf["musicbrainz_field_name"],
+                "converter": conf.get("converter", lambda v: v),
             }
+
     def from_musicbrainz(self, key, value):
-        return self._from_musicbrainz[key]['field_name'], self._from_musicbrainz[key]['converter'](value)
-
-registry = {
-    'Artist': Importer,
-    'Track': Importer,
-    'Album': Importer,
-    'Work': Importer,
-}
+        return (
+            self._from_musicbrainz[key]["field_name"],
+            self._from_musicbrainz[key]["converter"](value),
+        )
+
+
+registry = {"Artist": Importer, "Track": Importer, "Album": Importer, "Work": Importer}
diff --git a/api/funkwhale_api/music/lyrics.py b/api/funkwhale_api/music/lyrics.py
index 1ad69ce25f..4fd31d92cd 100644
--- a/api/funkwhale_api/music/lyrics.py
+++ b/api/funkwhale_api/music/lyrics.py
@@ -6,22 +6,22 @@ from bs4 import BeautifulSoup
 def _get_html(url):
     with urllib.request.urlopen(url) as response:
         html = response.read()
-    return html.decode('utf-8')
+    return html.decode("utf-8")
 
 
 def extract_content(html):
     soup = BeautifulSoup(html, "html.parser")
-    return soup.find_all("div", class_='lyricbox')[0].contents
+    return soup.find_all("div", class_="lyricbox")[0].contents
 
 
 def clean_content(contents):
     final_content = ""
     for e in contents:
-        if e == '\n':
+        if e == "\n":
             continue
-        if e.name == 'script':
+        if e.name == "script":
             continue
-        if e.name == 'br':
+        if e.name == "br":
             final_content += "\n"
             continue
         try:
diff --git a/api/funkwhale_api/music/management/commands/fix_track_files.py b/api/funkwhale_api/music/management/commands/fix_track_files.py
index c18e2b255a..94dd4fb9f7 100644
--- a/api/funkwhale_api/music/management/commands/fix_track_files.py
+++ b/api/funkwhale_api/music/management/commands/fix_track_files.py
@@ -10,20 +10,20 @@ from funkwhale_api.music import models, utils
 
 
 class Command(BaseCommand):
-    help = 'Run common checks and fix against imported tracks'
+    help = "Run common checks and fix against imported tracks"
 
     def add_arguments(self, parser):
         parser.add_argument(
-            '--dry-run',
-            action='store_true',
-            dest='dry_run',
+            "--dry-run",
+            action="store_true",
+            dest="dry_run",
             default=False,
-            help='Do not execute anything'
+            help="Do not execute anything",
         )
 
     def handle(self, *args, **options):
-        if options['dry_run']:
-            self.stdout.write('Dry-run on, will not commit anything')
+        if options["dry_run"]:
+            self.stdout.write("Dry-run on, will not commit anything")
         self.fix_mimetypes(**options)
         self.fix_file_data(**options)
         self.fix_file_size(**options)
@@ -31,75 +31,73 @@ class Command(BaseCommand):
 
     @transaction.atomic
     def fix_mimetypes(self, dry_run, **kwargs):
-        self.stdout.write('Fixing missing mimetypes...')
+        self.stdout.write("Fixing missing mimetypes...")
         matching = models.TrackFile.objects.filter(
-            source__startswith='file://').exclude(mimetype__startswith='audio/')
+            source__startswith="file://"
+        ).exclude(mimetype__startswith="audio/")
         self.stdout.write(
-            '[mimetypes] {} entries found with bad or no mimetype'.format(
-                matching.count()))
+            "[mimetypes] {} entries found with bad or no mimetype".format(
+                matching.count()
+            )
+        )
         for extension, mimetype in utils.EXTENSION_TO_MIMETYPE.items():
-            qs = matching.filter(source__endswith='.{}'.format(extension))
+            qs = matching.filter(source__endswith=".{}".format(extension))
             self.stdout.write(
-                '[mimetypes] setting {} {} files to {}'.format(
+                "[mimetypes] setting {} {} files to {}".format(
                     qs.count(), extension, mimetype
-                ))
+                )
+            )
             if not dry_run:
-                self.stdout.write('[mimetypes] commiting...')
+                self.stdout.write("[mimetypes] commiting...")
                 qs.update(mimetype=mimetype)
 
     def fix_file_data(self, dry_run, **kwargs):
-        self.stdout.write('Fixing missing bitrate or length...')
+        self.stdout.write("Fixing missing bitrate or length...")
         matching = models.TrackFile.objects.filter(
-            Q(bitrate__isnull=True) | Q(duration__isnull=True))
+            Q(bitrate__isnull=True) | Q(duration__isnull=True)
+        )
         total = matching.count()
         self.stdout.write(
-            '[bitrate/length] {} entries found with missing values'.format(
-                total))
+            "[bitrate/length] {} entries found with missing values".format(total)
+        )
         if dry_run:
             return
-        for i, tf in enumerate(matching.only('audio_file')):
+        for i, tf in enumerate(matching.only("audio_file")):
             self.stdout.write(
-                '[bitrate/length] {}/{} fixing file #{}'.format(
-                    i+1, total, tf.pk
-                ))
+                "[bitrate/length] {}/{} fixing file #{}".format(i + 1, total, tf.pk)
+            )
 
             try:
                 audio_file = tf.get_audio_file()
                 if audio_file:
                     with audio_file as f:
                         data = utils.get_audio_file_data(audio_file)
-                    tf.bitrate = data['bitrate']
-                    tf.duration = data['length']
-                    tf.save(update_fields=['duration', 'bitrate'])
+                    tf.bitrate = data["bitrate"]
+                    tf.duration = data["length"]
+                    tf.save(update_fields=["duration", "bitrate"])
                 else:
-                    self.stderr.write('[bitrate/length] no file found')
+                    self.stderr.write("[bitrate/length] no file found")
             except Exception as e:
                 self.stderr.write(
-                    '[bitrate/length] error with file #{}: {}'.format(
-                        tf.pk, str(e)
-                    )
+                    "[bitrate/length] error with file #{}: {}".format(tf.pk, str(e))
                 )
 
     def fix_file_size(self, dry_run, **kwargs):
-        self.stdout.write('Fixing missing size...')
+        self.stdout.write("Fixing missing size...")
         matching = models.TrackFile.objects.filter(size__isnull=True)
         total = matching.count()
-        self.stdout.write(
-            '[size] {} entries found with missing values'.format(total))
+        self.stdout.write("[size] {} entries found with missing values".format(total))
         if dry_run:
             return
-        for i, tf in enumerate(matching.only('size')):
+        for i, tf in enumerate(matching.only("size")):
             self.stdout.write(
-                '[size] {}/{} fixing file #{}'.format(
-                    i+1, total, tf.pk
-                ))
+                "[size] {}/{} fixing file #{}".format(i + 1, total, tf.pk)
+            )
 
             try:
                 tf.size = tf.get_file_size()
-                tf.save(update_fields=['size'])
+                tf.save(update_fields=["size"])
             except Exception as e:
                 self.stderr.write(
-                    '[size] error with file #{}: {}'.format(
-                        tf.pk, str(e)
-                    )
+                    "[size] error with file #{}: {}".format(tf.pk, str(e))
                 )
diff --git a/api/funkwhale_api/music/metadata.py b/api/funkwhale_api/music/metadata.py
index 4c17c42c0d..3a43521c58 100644
--- a/api/funkwhale_api/music/metadata.py
+++ b/api/funkwhale_api/music/metadata.py
@@ -14,21 +14,17 @@ class UnsupportedTag(KeyError):
 
 
 def get_id3_tag(f, k):
-    if k == 'pictures':
-        return f.tags.getall('APIC')
+    if k == "pictures":
+        return f.tags.getall("APIC")
     # First we try to grab the standard key
     try:
         return f.tags[k].text[0]
     except KeyError:
         pass
     # then we fallback on parsing non standard tags
-    all_tags = f.tags.getall('TXXX')
+    all_tags = f.tags.getall("TXXX")
     try:
-        matches = [
-            t
-            for t in all_tags
-            if t.desc.lower() == k.lower()
-        ]
+        matches = [t for t in all_tags if t.desc.lower() == k.lower()]
         return matches[0].text[0]
     except (KeyError, IndexError):
         raise TagNotFound(k)
@@ -37,17 +33,19 @@ def get_id3_tag(f, k):
 def clean_id3_pictures(apic):
     pictures = []
     for p in list(apic):
-        pictures.append({
-            'mimetype': p.mime,
-            'content': p.data,
-            'description': p.desc,
-            'type': p.type.real,
-        })
+        pictures.append(
+            {
+                "mimetype": p.mime,
+                "content": p.data,
+                "description": p.desc,
+                "type": p.type.real,
+            }
+        )
     return pictures
 
 
 def get_flac_tag(f, k):
-    if k == 'pictures':
+    if k == "pictures":
         return f.pictures
     try:
         return f.get(k, [])[0]
@@ -58,22 +56,22 @@ def get_flac_tag(f, k):
 def clean_flac_pictures(apic):
     pictures = []
     for p in list(apic):
-        pictures.append({
-            'mimetype': p.mime,
-            'content': p.data,
-            'description': p.desc,
-            'type': p.type.real,
-        })
+        pictures.append(
+            {
+                "mimetype": p.mime,
+                "content": p.data,
+                "description": p.desc,
+                "type": p.type.real,
+            }
+        )
     return pictures
 
 
 def get_mp3_recording_id(f, k):
     try:
-        return [
-            t
-            for t in f.tags.getall('UFID')
-            if 'musicbrainz.org' in t.owner
-        ][0].data.decode('utf-8')
+        return [t for t in f.tags.getall("UFID") if "musicbrainz.org" in t.owner][
+            0
+        ].data.decode("utf-8")
     except IndexError:
         raise TagNotFound(k)
 
@@ -86,18 +84,17 @@ def convert_track_number(v):
         pass
 
     try:
-        return int(v.split('/')[0])
+        return int(v.split("/")[0])
     except (ValueError, AttributeError, IndexError):
         pass
 
 
-
 class FirstUUIDField(forms.UUIDField):
     def to_python(self, value):
         try:
             # sometimes, Picard leaves to uuids in the field, separated
             # by a slash
-            value = value.split('/')[0]
+            value = value.split("/")[0]
         except (AttributeError, IndexError, TypeError):
             pass
 
@@ -105,150 +102,119 @@ class FirstUUIDField(forms.UUIDField):
 
 
 VALIDATION = {
-    'musicbrainz_artistid': FirstUUIDField(),
-    'musicbrainz_albumid': FirstUUIDField(),
-    'musicbrainz_recordingid': FirstUUIDField(),
+    "musicbrainz_artistid": FirstUUIDField(),
+    "musicbrainz_albumid": FirstUUIDField(),
+    "musicbrainz_recordingid": FirstUUIDField(),
 }
 
 CONF = {
-    'OggVorbis': {
-        'getter': lambda f, k: f[k][0],
-        'fields': {
-            'track_number': {
-                'field': 'TRACKNUMBER',
-                'to_application': convert_track_number
-            },
-            'title': {},
-            'artist': {},
-            'album': {},
-            'date': {
-                'field': 'date',
-                'to_application': lambda v: arrow.get(v).date()
-            },
-            'musicbrainz_albumid': {},
-            'musicbrainz_artistid': {},
-            'musicbrainz_recordingid': {
-                'field': 'musicbrainz_trackid'
-            },
-        }
+    "OggVorbis": {
+        "getter": lambda f, k: f[k][0],
+        "fields": {
+            "track_number": {
+                "field": "TRACKNUMBER",
+                "to_application": convert_track_number,
+            },
+            "title": {},
+            "artist": {},
+            "album": {},
+            "date": {"field": "date", "to_application": lambda v: arrow.get(v).date()},
+            "musicbrainz_albumid": {},
+            "musicbrainz_artistid": {},
+            "musicbrainz_recordingid": {"field": "musicbrainz_trackid"},
+        },
     },
-    'OggTheora': {
-        'getter': lambda f, k: f[k][0],
-        'fields': {
-            'track_number': {
-                'field': 'TRACKNUMBER',
-                'to_application': convert_track_number
-            },
-            'title': {},
-            'artist': {},
-            'album': {},
-            'date': {
-                'field': 'date',
-                'to_application': lambda v: arrow.get(v).date()
-            },
-            'musicbrainz_albumid': {
-                'field': 'MusicBrainz Album Id'
-            },
-            'musicbrainz_artistid': {
-                'field': 'MusicBrainz Artist Id'
-            },
-            'musicbrainz_recordingid': {
-                'field': 'MusicBrainz Track Id'
-            },
-        }
+    "OggTheora": {
+        "getter": lambda f, k: f[k][0],
+        "fields": {
+            "track_number": {
+                "field": "TRACKNUMBER",
+                "to_application": convert_track_number,
+            },
+            "title": {},
+            "artist": {},
+            "album": {},
+            "date": {"field": "date", "to_application": lambda v: arrow.get(v).date()},
+            "musicbrainz_albumid": {"field": "MusicBrainz Album Id"},
+            "musicbrainz_artistid": {"field": "MusicBrainz Artist Id"},
+            "musicbrainz_recordingid": {"field": "MusicBrainz Track Id"},
+        },
     },
-    'MP3': {
-        'getter': get_id3_tag,
-        'clean_pictures': clean_id3_pictures,
-        'fields': {
-            'track_number': {
-                'field': 'TRCK',
-                'to_application': convert_track_number
-            },
-            'title': {
-                'field': 'TIT2'
-            },
-            'artist': {
-                'field': 'TPE1'
-            },
-            'album': {
-                'field': 'TALB'
-            },
-            'date': {
-                'field': 'TDRC',
-                'to_application': lambda v: arrow.get(str(v)).date()
-            },
-            'musicbrainz_albumid': {
-                'field': 'MusicBrainz Album Id'
-            },
-            'musicbrainz_artistid': {
-                'field': 'MusicBrainz Artist Id'
-            },
-            'musicbrainz_recordingid': {
-                'field': 'UFID',
-                'getter': get_mp3_recording_id,
-            },
-            'pictures': {},
-        }
+    "MP3": {
+        "getter": get_id3_tag,
+        "clean_pictures": clean_id3_pictures,
+        "fields": {
+            "track_number": {"field": "TRCK", "to_application": convert_track_number},
+            "title": {"field": "TIT2"},
+            "artist": {"field": "TPE1"},
+            "album": {"field": "TALB"},
+            "date": {
+                "field": "TDRC",
+                "to_application": lambda v: arrow.get(str(v)).date(),
+            },
+            "musicbrainz_albumid": {"field": "MusicBrainz Album Id"},
+            "musicbrainz_artistid": {"field": "MusicBrainz Artist Id"},
+            "musicbrainz_recordingid": {
+                "field": "UFID",
+                "getter": get_mp3_recording_id,
+            },
+            "pictures": {},
+        },
     },
-    'FLAC': {
-        'getter': get_flac_tag,
-        'clean_pictures': clean_flac_pictures,
-        'fields': {
-            'track_number': {
-                'field': 'tracknumber',
-                'to_application': convert_track_number
-            },
-            'title': {},
-            'artist': {},
-            'album': {},
-            'date': {
-                'field': 'date',
-                'to_application': lambda v: arrow.get(str(v)).date()
-            },
-            'musicbrainz_albumid': {},
-            'musicbrainz_artistid': {},
-            'musicbrainz_recordingid': {
-                'field': 'musicbrainz_trackid'
-            },
-            'test': {},
-            'pictures': {},
-        }
+    "FLAC": {
+        "getter": get_flac_tag,
+        "clean_pictures": clean_flac_pictures,
+        "fields": {
+            "track_number": {
+                "field": "tracknumber",
+                "to_application": convert_track_number,
+            },
+            "title": {},
+            "artist": {},
+            "album": {},
+            "date": {
+                "field": "date",
+                "to_application": lambda v: arrow.get(str(v)).date(),
+            },
+            "musicbrainz_albumid": {},
+            "musicbrainz_artistid": {},
+            "musicbrainz_recordingid": {"field": "musicbrainz_trackid"},
+            "test": {},
+            "pictures": {},
+        },
     },
 }
 
 
 class Metadata(object):
-
     def __init__(self, path):
         self._file = mutagen.File(path)
         if self._file is None:
-            raise ValueError('Cannot parse metadata from {}'.format(path))
+            raise ValueError("Cannot parse metadata from {}".format(path))
         ft = self.get_file_type(self._file)
         try:
             self._conf = CONF[ft]
         except KeyError:
-            raise ValueError('Unsupported format {}'.format(ft))
+            raise ValueError("Unsupported format {}".format(ft))
 
     def get_file_type(self, f):
         return f.__class__.__name__
 
     def get(self, key, default=NODEFAULT):
         try:
-            field_conf = self._conf['fields'][key]
+            field_conf = self._conf["fields"][key]
         except KeyError:
-            raise UnsupportedTag(
-                '{} is not supported for this file format'.format(key))
-        real_key = field_conf.get('field', key)
+            raise UnsupportedTag("{} is not supported for this file format".format(key))
+        real_key = field_conf.get("field", key)
         try:
-            getter = field_conf.get('getter', self._conf['getter'])
+            getter = field_conf.get("getter", self._conf["getter"])
             v = getter(self._file, real_key)
         except KeyError:
             if default == NODEFAULT:
                 raise TagNotFound(real_key)
             return default
 
-        converter = field_conf.get('to_application')
+        converter = field_conf.get("to_application")
         if converter:
             v = converter(v)
         field = VALIDATION.get(key)
@@ -256,15 +222,15 @@ class Metadata(object):
             v = field.to_python(v)
         return v
 
-    def get_picture(self, picture_type='cover_front'):
+    def get_picture(self, picture_type="cover_front"):
         ptype = getattr(mutagen.id3.PictureType, picture_type.upper())
         try:
-            pictures = self.get('pictures')
+            pictures = self.get("pictures")
         except (UnsupportedTag, TagNotFound):
             return
 
-        cleaner = self._conf.get('clean_pictures', lambda v: v)
+        cleaner = self._conf.get("clean_pictures", lambda v: v)
         pictures = cleaner(pictures)
         for p in pictures:
-            if p['type'] == ptype:
+            if p["type"] == ptype:
                 return p
diff --git a/api/funkwhale_api/music/migrations/0001_initial.py b/api/funkwhale_api/music/migrations/0001_initial.py
index 265b81577f..0bb12342de 100644
--- a/api/funkwhale_api/music/migrations/0001_initial.py
+++ b/api/funkwhale_api/music/migrations/0001_initial.py
@@ -8,82 +8,183 @@ import django.utils.timezone
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-    ]
+    dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
 
     operations = [
         migrations.CreateModel(
-            name='Album',
+            name="Album",
             fields=[
-                ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')),
-                ('mbid', models.UUIDField(editable=False, blank=True, null=True)),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('title', models.CharField(max_length=255)),
-                ('release_date', models.DateField()),
-                ('type', models.CharField(default='album', choices=[('album', 'Album')], max_length=30)),
+                (
+                    "id",
+                    models.AutoField(
+                        primary_key=True,
+                        auto_created=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("mbid", models.UUIDField(editable=False, blank=True, null=True)),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                ("title", models.CharField(max_length=255)),
+                ("release_date", models.DateField()),
+                (
+                    "type",
+                    models.CharField(
+                        default="album", choices=[("album", "Album")], max_length=30
+                    ),
+                ),
             ],
-            options={
-                'abstract': False,
-            },
+            options={"abstract": False},
         ),
         migrations.CreateModel(
-            name='Artist',
+            name="Artist",
             fields=[
-                ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')),
-                ('mbid', models.UUIDField(editable=False, blank=True, null=True)),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('name', models.CharField(max_length=255)),
+                (
+                    "id",
+                    models.AutoField(
+                        primary_key=True,
+                        auto_created=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("mbid", models.UUIDField(editable=False, blank=True, null=True)),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                ("name", models.CharField(max_length=255)),
             ],
-            options={
-                'abstract': False,
-            },
+            options={"abstract": False},
         ),
         migrations.CreateModel(
-            name='ImportBatch',
+            name="ImportBatch",
             fields=[
-                ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('submitted_by', models.ForeignKey(related_name='imports', to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE)),
+                (
+                    "id",
+                    models.AutoField(
+                        primary_key=True,
+                        auto_created=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                (
+                    "submitted_by",
+                    models.ForeignKey(
+                        related_name="imports",
+                        to=settings.AUTH_USER_MODEL,
+                        on_delete=models.CASCADE,
+                    ),
+                ),
             ],
         ),
         migrations.CreateModel(
-            name='ImportJob',
+            name="ImportJob",
             fields=[
-                ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')),
-                ('source', models.URLField()),
-                ('mbid', models.UUIDField(editable=False)),
-                ('status', models.CharField(default='pending', choices=[('pending', 'Pending'), ('finished', 'finished')], max_length=30)),
-                ('batch', models.ForeignKey(related_name='jobs', to='music.ImportBatch', on_delete=models.CASCADE)),
+                (
+                    "id",
+                    models.AutoField(
+                        primary_key=True,
+                        auto_created=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("source", models.URLField()),
+                ("mbid", models.UUIDField(editable=False)),
+                (
+                    "status",
+                    models.CharField(
+                        default="pending",
+                        choices=[("pending", "Pending"), ("finished", "finished")],
+                        max_length=30,
+                    ),
+                ),
+                (
+                    "batch",
+                    models.ForeignKey(
+                        related_name="jobs",
+                        to="music.ImportBatch",
+                        on_delete=models.CASCADE,
+                    ),
+                ),
             ],
         ),
         migrations.CreateModel(
-            name='Track',
+            name="Track",
             fields=[
-                ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')),
-                ('mbid', models.UUIDField(editable=False, blank=True, null=True)),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('title', models.CharField(max_length=255)),
-                ('album', models.ForeignKey(related_name='tracks', blank=True, null=True, to='music.Album', on_delete=models.CASCADE)),
-                ('artist', models.ForeignKey(related_name='tracks', to='music.Artist', on_delete=models.CASCADE)),
+                (
+                    "id",
+                    models.AutoField(
+                        primary_key=True,
+                        auto_created=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("mbid", models.UUIDField(editable=False, blank=True, null=True)),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                ("title", models.CharField(max_length=255)),
+                (
+                    "album",
+                    models.ForeignKey(
+                        related_name="tracks",
+                        blank=True,
+                        null=True,
+                        to="music.Album",
+                        on_delete=models.CASCADE,
+                    ),
+                ),
+                (
+                    "artist",
+                    models.ForeignKey(
+                        related_name="tracks",
+                        to="music.Artist",
+                        on_delete=models.CASCADE,
+                    ),
+                ),
             ],
-            options={
-                'abstract': False,
-            },
+            options={"abstract": False},
         ),
         migrations.CreateModel(
-            name='TrackFile',
+            name="TrackFile",
             fields=[
-                ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')),
-                ('audio_file', models.FileField(upload_to='tracks')),
-                ('source', models.URLField(blank=True, null=True)),
-                ('duration', models.IntegerField(blank=True, null=True)),
-                ('track', models.ForeignKey(related_name='files', to='music.Track', on_delete=models.CASCADE)),
+                (
+                    "id",
+                    models.AutoField(
+                        primary_key=True,
+                        auto_created=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("audio_file", models.FileField(upload_to="tracks")),
+                ("source", models.URLField(blank=True, null=True)),
+                ("duration", models.IntegerField(blank=True, null=True)),
+                (
+                    "track",
+                    models.ForeignKey(
+                        related_name="files", to="music.Track", on_delete=models.CASCADE
+                    ),
+                ),
             ],
         ),
         migrations.AddField(
-            model_name='album',
-            name='artist',
-            field=models.ForeignKey(related_name='albums', to='music.Artist', on_delete=models.CASCADE),
+            model_name="album",
+            name="artist",
+            field=models.ForeignKey(
+                related_name="albums", to="music.Artist", on_delete=models.CASCADE
+            ),
         ),
     ]
diff --git a/api/funkwhale_api/music/migrations/0002_auto_20151215_1645.py b/api/funkwhale_api/music/migrations/0002_auto_20151215_1645.py
index 1b54a5cfc7..094c679dad 100644
--- a/api/funkwhale_api/music/migrations/0002_auto_20151215_1645.py
+++ b/api/funkwhale_api/music/migrations/0002_auto_20151215_1645.py
@@ -6,35 +6,31 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0001_initial'),
-    ]
+    dependencies = [("music", "0001_initial")]
 
     operations = [
         migrations.AlterModelOptions(
-            name='album',
-            options={'ordering': ['-creation_date']},
+            name="album", options={"ordering": ["-creation_date"]}
         ),
         migrations.AlterModelOptions(
-            name='artist',
-            options={'ordering': ['-creation_date']},
+            name="artist", options={"ordering": ["-creation_date"]}
         ),
         migrations.AlterModelOptions(
-            name='importbatch',
-            options={'ordering': ['-creation_date']},
+            name="importbatch", options={"ordering": ["-creation_date"]}
         ),
         migrations.AlterModelOptions(
-            name='track',
-            options={'ordering': ['-creation_date']},
+            name="track", options={"ordering": ["-creation_date"]}
         ),
         migrations.AddField(
-            model_name='album',
-            name='cover',
-            field=models.ImageField(upload_to='albums/covers/%Y/%m/%d', null=True, blank=True),
+            model_name="album",
+            name="cover",
+            field=models.ImageField(
+                upload_to="albums/covers/%Y/%m/%d", null=True, blank=True
+            ),
         ),
         migrations.AlterField(
-            model_name='trackfile',
-            name='audio_file',
-            field=models.FileField(upload_to='tracks/%Y/%m/%d'),
+            model_name="trackfile",
+            name="audio_file",
+            field=models.FileField(upload_to="tracks/%Y/%m/%d"),
         ),
     ]
diff --git a/api/funkwhale_api/music/migrations/0003_auto_20151222_2233.py b/api/funkwhale_api/music/migrations/0003_auto_20151222_2233.py
index 060957dc16..d8337a7816 100644
--- a/api/funkwhale_api/music/migrations/0003_auto_20151222_2233.py
+++ b/api/funkwhale_api/music/migrations/0003_auto_20151222_2233.py
@@ -6,14 +6,10 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0002_auto_20151215_1645'),
-    ]
+    dependencies = [("music", "0002_auto_20151215_1645")]
 
     operations = [
         migrations.AlterField(
-            model_name='album',
-            name='release_date',
-            field=models.DateField(null=True),
-        ),
+            model_name="album", name="release_date", field=models.DateField(null=True)
+        )
     ]
diff --git a/api/funkwhale_api/music/migrations/0004_track_tags.py b/api/funkwhale_api/music/migrations/0004_track_tags.py
index f95b08b0e8..c00f5edfe9 100644
--- a/api/funkwhale_api/music/migrations/0004_track_tags.py
+++ b/api/funkwhale_api/music/migrations/0004_track_tags.py
@@ -8,14 +8,19 @@ import taggit.managers
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('taggit', '0002_auto_20150616_2121'),
-        ('music', '0003_auto_20151222_2233'),
+        ("taggit", "0002_auto_20150616_2121"),
+        ("music", "0003_auto_20151222_2233"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='track',
-            name='tags',
-            field=taggit.managers.TaggableManager(verbose_name='Tags', help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag'),
-        ),
+            model_name="track",
+            name="tags",
+            field=taggit.managers.TaggableManager(
+                verbose_name="Tags",
+                help_text="A comma-separated list of tags.",
+                through="taggit.TaggedItem",
+                to="taggit.Tag",
+            ),
+        )
     ]
diff --git a/api/funkwhale_api/music/migrations/0005_deduplicate.py b/api/funkwhale_api/music/migrations/0005_deduplicate.py
index 82dca0caac..0dfdd78f40 100644
--- a/api/funkwhale_api/music/migrations/0005_deduplicate.py
+++ b/api/funkwhale_api/music/migrations/0005_deduplicate.py
@@ -5,7 +5,14 @@ from django.db import migrations, models
 
 
 def get_duplicates(model):
-    return [i['mbid'] for i in model.objects.values('mbid').annotate(idcount=models.Count('mbid')).order_by('-idcount') if i['idcount'] > 1]
+    return [
+        i["mbid"]
+        for i in model.objects.values("mbid")
+        .annotate(idcount=models.Count("mbid"))
+        .order_by("-idcount")
+        if i["idcount"] > 1
+    ]
+
 
 def deduplicate(apps, schema_editor):
     Artist = apps.get_model("music", "Artist")
@@ -13,28 +20,25 @@ def deduplicate(apps, schema_editor):
     Track = apps.get_model("music", "Track")
 
     for mbid in get_duplicates(Artist):
-        ref = Artist.objects.filter(mbid=mbid).order_by('pk').first()
+        ref = Artist.objects.filter(mbid=mbid).order_by("pk").first()
         duplicates = Artist.objects.filter(mbid=mbid).exclude(pk=ref.pk)
         Album.objects.filter(artist__in=duplicates).update(artist=ref)
         Track.objects.filter(artist__in=duplicates).update(artist=ref)
         duplicates.delete()
 
     for mbid in get_duplicates(Album):
-        ref = Album.objects.filter(mbid=mbid).order_by('pk').first()
+        ref = Album.objects.filter(mbid=mbid).order_by("pk").first()
         duplicates = Album.objects.filter(mbid=mbid).exclude(pk=ref.pk)
         Track.objects.filter(album__in=duplicates).update(album=ref)
         duplicates.delete()
 
+
 def rewind(*args, **kwargs):
     pass
 
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0004_track_tags'),
-    ]
+    dependencies = [("music", "0004_track_tags")]
 
-    operations = [
-        migrations.RunPython(deduplicate, rewind),
-    ]
+    operations = [migrations.RunPython(deduplicate, rewind)]
diff --git a/api/funkwhale_api/music/migrations/0006_unique_mbid.py b/api/funkwhale_api/music/migrations/0006_unique_mbid.py
index e13e3a743b..7d926e3731 100644
--- a/api/funkwhale_api/music/migrations/0006_unique_mbid.py
+++ b/api/funkwhale_api/music/migrations/0006_unique_mbid.py
@@ -3,26 +3,31 @@ from __future__ import unicode_literals
 
 from django.db import migrations, models
 
+
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0005_deduplicate'),
-    ]
+    dependencies = [("music", "0005_deduplicate")]
 
     operations = [
         migrations.AlterField(
-            model_name='album',
-            name='mbid',
-            field=models.UUIDField(null=True, editable=False, unique=True, blank=True, db_index=True),
+            model_name="album",
+            name="mbid",
+            field=models.UUIDField(
+                null=True, editable=False, unique=True, blank=True, db_index=True
+            ),
         ),
         migrations.AlterField(
-            model_name='artist',
-            name='mbid',
-            field=models.UUIDField(null=True, editable=False, unique=True, blank=True, db_index=True),
+            model_name="artist",
+            name="mbid",
+            field=models.UUIDField(
+                null=True, editable=False, unique=True, blank=True, db_index=True
+            ),
         ),
         migrations.AlterField(
-            model_name='track',
-            name='mbid',
-            field=models.UUIDField(null=True, editable=False, unique=True, blank=True, db_index=True),
+            model_name="track",
+            name="mbid",
+            field=models.UUIDField(
+                null=True, editable=False, unique=True, blank=True, db_index=True
+            ),
         ),
     ]
diff --git a/api/funkwhale_api/music/migrations/0007_track_position.py b/api/funkwhale_api/music/migrations/0007_track_position.py
index 089e0128f3..d43dcaea38 100644
--- a/api/funkwhale_api/music/migrations/0007_track_position.py
+++ b/api/funkwhale_api/music/migrations/0007_track_position.py
@@ -6,14 +6,12 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0006_unique_mbid'),
-    ]
+    dependencies = [("music", "0006_unique_mbid")]
 
     operations = [
         migrations.AddField(
-            model_name='track',
-            name='position',
+            model_name="track",
+            name="position",
             field=models.PositiveIntegerField(blank=True, null=True),
-        ),
+        )
     ]
diff --git a/api/funkwhale_api/music/migrations/0008_auto_20160529_1456.py b/api/funkwhale_api/music/migrations/0008_auto_20160529_1456.py
index e7fa5c8f48..8812c65a45 100644
--- a/api/funkwhale_api/music/migrations/0008_auto_20160529_1456.py
+++ b/api/funkwhale_api/music/migrations/0008_auto_20160529_1456.py
@@ -6,24 +6,22 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0007_track_position'),
-    ]
+    dependencies = [("music", "0007_track_position")]
 
     operations = [
         migrations.AlterField(
-            model_name='album',
-            name='mbid',
+            model_name="album",
+            name="mbid",
             field=models.UUIDField(null=True, db_index=True, unique=True, blank=True),
         ),
         migrations.AlterField(
-            model_name='artist',
-            name='mbid',
+            model_name="artist",
+            name="mbid",
             field=models.UUIDField(null=True, db_index=True, unique=True, blank=True),
         ),
         migrations.AlterField(
-            model_name='track',
-            name='mbid',
+            model_name="track",
+            name="mbid",
             field=models.UUIDField(null=True, db_index=True, unique=True, blank=True),
         ),
     ]
diff --git a/api/funkwhale_api/music/migrations/0009_auto_20160920_1614.py b/api/funkwhale_api/music/migrations/0009_auto_20160920_1614.py
index 3a3d93989c..2999df60cc 100644
--- a/api/funkwhale_api/music/migrations/0009_auto_20160920_1614.py
+++ b/api/funkwhale_api/music/migrations/0009_auto_20160920_1614.py
@@ -8,42 +8,71 @@ import versatileimagefield.fields
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0008_auto_20160529_1456'),
-    ]
+    dependencies = [("music", "0008_auto_20160529_1456")]
 
     operations = [
         migrations.CreateModel(
-            name='Lyrics',
+            name="Lyrics",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)),
-                ('url', models.URLField()),
-                ('content', models.TextField(null=True, blank=True)),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        verbose_name="ID",
+                        serialize=False,
+                    ),
+                ),
+                ("url", models.URLField()),
+                ("content", models.TextField(null=True, blank=True)),
             ],
         ),
         migrations.CreateModel(
-            name='Work',
+            name="Work",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)),
-                ('mbid', models.UUIDField(unique=True, null=True, db_index=True, blank=True)),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('language', models.CharField(max_length=20)),
-                ('nature', models.CharField(max_length=50)),
-                ('title', models.CharField(max_length=255)),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        verbose_name="ID",
+                        serialize=False,
+                    ),
+                ),
+                (
+                    "mbid",
+                    models.UUIDField(unique=True, null=True, db_index=True, blank=True),
+                ),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                ("language", models.CharField(max_length=20)),
+                ("nature", models.CharField(max_length=50)),
+                ("title", models.CharField(max_length=255)),
             ],
-            options={
-                'ordering': ['-creation_date'],
-                'abstract': False,
-            },
+            options={"ordering": ["-creation_date"], "abstract": False},
         ),
         migrations.AddField(
-            model_name='lyrics',
-            name='work',
-            field=models.ForeignKey(related_name='lyrics', to='music.Work', blank=True, null=True, on_delete=models.CASCADE),
+            model_name="lyrics",
+            name="work",
+            field=models.ForeignKey(
+                related_name="lyrics",
+                to="music.Work",
+                blank=True,
+                null=True,
+                on_delete=models.CASCADE,
+            ),
         ),
         migrations.AddField(
-            model_name='track',
-            name='work',
-            field=models.ForeignKey(related_name='tracks', to='music.Work', blank=True, null=True, on_delete=models.CASCADE),
+            model_name="track",
+            name="work",
+            field=models.ForeignKey(
+                related_name="tracks",
+                to="music.Work",
+                blank=True,
+                null=True,
+                on_delete=models.CASCADE,
+            ),
         ),
     ]
diff --git a/api/funkwhale_api/music/migrations/0010_auto_20160920_1742.py b/api/funkwhale_api/music/migrations/0010_auto_20160920_1742.py
index 03ac057935..dcb4f79504 100644
--- a/api/funkwhale_api/music/migrations/0010_auto_20160920_1742.py
+++ b/api/funkwhale_api/music/migrations/0010_auto_20160920_1742.py
@@ -7,14 +7,10 @@ import versatileimagefield.fields
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0009_auto_20160920_1614'),
-    ]
+    dependencies = [("music", "0009_auto_20160920_1614")]
 
     operations = [
         migrations.AlterField(
-            model_name='lyrics',
-            name='url',
-            field=models.URLField(unique=True),
-        ),
+            model_name="lyrics", name="url", field=models.URLField(unique=True)
+        )
     ]
diff --git a/api/funkwhale_api/music/migrations/0011_rename_files.py b/api/funkwhale_api/music/migrations/0011_rename_files.py
index 1c59535f5e..7152cca3b1 100644
--- a/api/funkwhale_api/music/migrations/0011_rename_files.py
+++ b/api/funkwhale_api/music/migrations/0011_rename_files.py
@@ -47,15 +47,13 @@ def rewind(apps, schema_editor):
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0010_auto_20160920_1742'),
-    ]
+    dependencies = [("music", "0010_auto_20160920_1742")]
 
     operations = [
         migrations.AlterField(
-            model_name='trackfile',
-            name='audio_file',
-            field=models.FileField(upload_to='tracks/%Y/%m/%d', max_length=255),
+            model_name="trackfile",
+            name="audio_file",
+            field=models.FileField(upload_to="tracks/%Y/%m/%d", max_length=255),
         ),
         migrations.RunPython(rename_files, rewind),
     ]
diff --git a/api/funkwhale_api/music/migrations/0012_auto_20161122_1905.py b/api/funkwhale_api/music/migrations/0012_auto_20161122_1905.py
index 8d7e25246b..016d1c110d 100644
--- a/api/funkwhale_api/music/migrations/0012_auto_20161122_1905.py
+++ b/api/funkwhale_api/music/migrations/0012_auto_20161122_1905.py
@@ -7,14 +7,14 @@ import versatileimagefield.fields
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0011_rename_files'),
-    ]
+    dependencies = [("music", "0011_rename_files")]
 
     operations = [
         migrations.AlterField(
-            model_name='album',
-            name='cover',
-            field=versatileimagefield.fields.VersatileImageField(null=True, blank=True, upload_to='albums/covers/%Y/%m/%d'),
-        ),
+            model_name="album",
+            name="cover",
+            field=versatileimagefield.fields.VersatileImageField(
+                null=True, blank=True, upload_to="albums/covers/%Y/%m/%d"
+            ),
+        )
     ]
diff --git a/api/funkwhale_api/music/migrations/0013_auto_20171213_2211.py b/api/funkwhale_api/music/migrations/0013_auto_20171213_2211.py
index 00ccbb6218..2874aa81f9 100644
--- a/api/funkwhale_api/music/migrations/0013_auto_20171213_2211.py
+++ b/api/funkwhale_api/music/migrations/0013_auto_20171213_2211.py
@@ -7,22 +7,16 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0012_auto_20161122_1905'),
-    ]
+    dependencies = [("music", "0012_auto_20161122_1905")]
 
     operations = [
+        migrations.AlterModelOptions(name="importjob", options={"ordering": ("id",)}),
         migrations.AlterModelOptions(
-            name='importjob',
-            options={'ordering': ('id',)},
-        ),
-        migrations.AlterModelOptions(
-            name='track',
-            options={'ordering': ['album', 'position']},
+            name="track", options={"ordering": ["album", "position"]}
         ),
         migrations.AddField(
-            model_name='album',
-            name='release_group_id',
+            model_name="album",
+            name="release_group_id",
             field=models.UUIDField(blank=True, null=True),
         ),
     ]
diff --git a/api/funkwhale_api/music/migrations/0014_importjob_track_file.py b/api/funkwhale_api/music/migrations/0014_importjob_track_file.py
index 6950fd3c1d..004e247eae 100644
--- a/api/funkwhale_api/music/migrations/0014_importjob_track_file.py
+++ b/api/funkwhale_api/music/migrations/0014_importjob_track_file.py
@@ -8,14 +8,18 @@ import django.db.models.deletion
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0013_auto_20171213_2211'),
-    ]
+    dependencies = [("music", "0013_auto_20171213_2211")]
 
     operations = [
         migrations.AddField(
-            model_name='importjob',
-            name='track_file',
-            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='jobs', to='music.TrackFile'),
-        ),
+            model_name="importjob",
+            name="track_file",
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="jobs",
+                to="music.TrackFile",
+            ),
+        )
     ]
diff --git a/api/funkwhale_api/music/migrations/0015_bind_track_file_to_import_job.py b/api/funkwhale_api/music/migrations/0015_bind_track_file_to_import_job.py
index edb5e6470d..9762864f1e 100644
--- a/api/funkwhale_api/music/migrations/0015_bind_track_file_to_import_job.py
+++ b/api/funkwhale_api/music/migrations/0015_bind_track_file_to_import_job.py
@@ -10,13 +10,13 @@ def bind_jobs(apps, schema_editor):
     TrackFile = apps.get_model("music", "TrackFile")
     ImportJob = apps.get_model("music", "ImportJob")
 
-    for job in ImportJob.objects.all().only('mbid'):
+    for job in ImportJob.objects.all().only("mbid"):
         f = TrackFile.objects.filter(track__mbid=job.mbid).first()
         if not f:
-            print('No file for mbid {}'.format(job.mbid))
+            print("No file for mbid {}".format(job.mbid))
             continue
         job.track_file = f
-        job.save(update_fields=['track_file'])
+        job.save(update_fields=["track_file"])
 
 
 def rewind(apps, schema_editor):
@@ -25,10 +25,6 @@ def rewind(apps, schema_editor):
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0014_importjob_track_file'),
-    ]
+    dependencies = [("music", "0014_importjob_track_file")]
 
-    operations = [
-        migrations.RunPython(bind_jobs, rewind),
-    ]
+    operations = [migrations.RunPython(bind_jobs, rewind)]
diff --git a/api/funkwhale_api/music/migrations/0016_trackfile_acoustid_track_id.py b/api/funkwhale_api/music/migrations/0016_trackfile_acoustid_track_id.py
index 21d8ce8ea4..467fb0eef0 100644
--- a/api/funkwhale_api/music/migrations/0016_trackfile_acoustid_track_id.py
+++ b/api/funkwhale_api/music/migrations/0016_trackfile_acoustid_track_id.py
@@ -5,14 +5,12 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0015_bind_track_file_to_import_job'),
-    ]
+    dependencies = [("music", "0015_bind_track_file_to_import_job")]
 
     operations = [
         migrations.AddField(
-            model_name='trackfile',
-            name='acoustid_track_id',
+            model_name="trackfile",
+            name="acoustid_track_id",
             field=models.UUIDField(blank=True, null=True),
-        ),
+        )
     ]
diff --git a/api/funkwhale_api/music/migrations/0017_auto_20171227_1728.py b/api/funkwhale_api/music/migrations/0017_auto_20171227_1728.py
index dfca664370..10a8ed1e8c 100644
--- a/api/funkwhale_api/music/migrations/0017_auto_20171227_1728.py
+++ b/api/funkwhale_api/music/migrations/0017_auto_20171227_1728.py
@@ -5,24 +5,28 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0016_trackfile_acoustid_track_id'),
-    ]
+    dependencies = [("music", "0016_trackfile_acoustid_track_id")]
 
     operations = [
         migrations.AddField(
-            model_name='importbatch',
-            name='source',
-            field=models.CharField(choices=[('api', 'api'), ('shell', 'shell')], default='api', max_length=30),
+            model_name="importbatch",
+            name="source",
+            field=models.CharField(
+                choices=[("api", "api"), ("shell", "shell")],
+                default="api",
+                max_length=30,
+            ),
         ),
         migrations.AddField(
-            model_name='importjob',
-            name='audio_file',
-            field=models.FileField(blank=True, max_length=255, null=True, upload_to='imports/%Y/%m/%d'),
+            model_name="importjob",
+            name="audio_file",
+            field=models.FileField(
+                blank=True, max_length=255, null=True, upload_to="imports/%Y/%m/%d"
+            ),
         ),
         migrations.AlterField(
-            model_name='importjob',
-            name='mbid',
+            model_name="importjob",
+            name="mbid",
             field=models.UUIDField(blank=True, editable=False, null=True),
         ),
     ]
diff --git a/api/funkwhale_api/music/migrations/0018_auto_20180218_1554.py b/api/funkwhale_api/music/migrations/0018_auto_20180218_1554.py
index c45298798b..bfc26b0117 100644
--- a/api/funkwhale_api/music/migrations/0018_auto_20180218_1554.py
+++ b/api/funkwhale_api/music/migrations/0018_auto_20180218_1554.py
@@ -5,24 +5,31 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0017_auto_20171227_1728'),
-    ]
+    dependencies = [("music", "0017_auto_20171227_1728")]
 
     operations = [
         migrations.AddField(
-            model_name='trackfile',
-            name='mimetype',
+            model_name="trackfile",
+            name="mimetype",
             field=models.CharField(blank=True, max_length=200, null=True),
         ),
         migrations.AlterField(
-            model_name='importjob',
-            name='source',
+            model_name="importjob",
+            name="source",
             field=models.CharField(max_length=500),
         ),
         migrations.AlterField(
-            model_name='importjob',
-            name='status',
-            field=models.CharField(choices=[('pending', 'Pending'), ('finished', 'Finished'), ('errored', 'Errored'), ('skipped', 'Skipped')], default='pending', max_length=30),
+            model_name="importjob",
+            name="status",
+            field=models.CharField(
+                choices=[
+                    ("pending", "Pending"),
+                    ("finished", "Finished"),
+                    ("errored", "Errored"),
+                    ("skipped", "Skipped"),
+                ],
+                default="pending",
+                max_length=30,
+            ),
         ),
     ]
diff --git a/api/funkwhale_api/music/migrations/0019_populate_mimetypes.py b/api/funkwhale_api/music/migrations/0019_populate_mimetypes.py
index 127aa5e69a..55f9a127ad 100644
--- a/api/funkwhale_api/music/migrations/0019_populate_mimetypes.py
+++ b/api/funkwhale_api/music/migrations/0019_populate_mimetypes.py
@@ -9,14 +9,16 @@ from funkwhale_api.music.utils import guess_mimetype
 def populate_mimetype(apps, schema_editor):
     TrackFile = apps.get_model("music", "TrackFile")
 
-    for tf in TrackFile.objects.filter(audio_file__isnull=False, mimetype__isnull=True).only('audio_file'):
+    for tf in TrackFile.objects.filter(
+        audio_file__isnull=False, mimetype__isnull=True
+    ).only("audio_file"):
         try:
             tf.mimetype = guess_mimetype(tf.audio_file)
         except Exception as e:
-            print('Error on track file {}: {}'.format(tf.pk, e))
+            print("Error on track file {}: {}".format(tf.pk, e))
             continue
-        print('Track file {}: {}'.format(tf.pk, tf.mimetype))
-        tf.save(update_fields=['mimetype'])
+        print("Track file {}: {}".format(tf.pk, tf.mimetype))
+        tf.save(update_fields=["mimetype"])
 
 
 def rewind(apps, schema_editor):
@@ -25,10 +27,6 @@ def rewind(apps, schema_editor):
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0018_auto_20180218_1554'),
-    ]
+    dependencies = [("music", "0018_auto_20180218_1554")]
 
-    operations = [
-        migrations.RunPython(populate_mimetype, rewind),
-    ]
+    operations = [migrations.RunPython(populate_mimetype, rewind)]
diff --git a/api/funkwhale_api/music/migrations/0020_importbatch_status.py b/api/funkwhale_api/music/migrations/0020_importbatch_status.py
index 265d1ba5d5..e02aa0859f 100644
--- a/api/funkwhale_api/music/migrations/0020_importbatch_status.py
+++ b/api/funkwhale_api/music/migrations/0020_importbatch_status.py
@@ -5,14 +5,21 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0019_populate_mimetypes'),
-    ]
+    dependencies = [("music", "0019_populate_mimetypes")]
 
     operations = [
         migrations.AddField(
-            model_name='importbatch',
-            name='status',
-            field=models.CharField(choices=[('pending', 'Pending'), ('finished', 'Finished'), ('errored', 'Errored'), ('skipped', 'Skipped')], default='pending', max_length=30),
-        ),
+            model_name="importbatch",
+            name="status",
+            field=models.CharField(
+                choices=[
+                    ("pending", "Pending"),
+                    ("finished", "Finished"),
+                    ("errored", "Errored"),
+                    ("skipped", "Skipped"),
+                ],
+                default="pending",
+                max_length=30,
+            ),
+        )
     ]
diff --git a/api/funkwhale_api/music/migrations/0021_populate_batch_status.py b/api/funkwhale_api/music/migrations/0021_populate_batch_status.py
index 061d649b06..5bda0edfbd 100644
--- a/api/funkwhale_api/music/migrations/0021_populate_batch_status.py
+++ b/api/funkwhale_api/music/migrations/0021_populate_batch_status.py
@@ -7,11 +7,12 @@ from django.db import migrations, models
 
 def populate_status(apps, schema_editor):
     from funkwhale_api.music.utils import compute_status
+
     ImportBatch = apps.get_model("music", "ImportBatch")
 
-    for ib in ImportBatch.objects.prefetch_related('jobs'):
+    for ib in ImportBatch.objects.prefetch_related("jobs"):
         ib.status = compute_status(ib.jobs.all())
-        ib.save(update_fields=['status'])
+        ib.save(update_fields=["status"])
 
 
 def rewind(apps, schema_editor):
@@ -20,10 +21,6 @@ def rewind(apps, schema_editor):
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0020_importbatch_status'),
-    ]
+    dependencies = [("music", "0020_importbatch_status")]
 
-    operations = [
-        migrations.RunPython(populate_status, rewind),
-    ]
+    operations = [migrations.RunPython(populate_status, rewind)]
diff --git a/api/funkwhale_api/music/migrations/0022_importbatch_import_request.py b/api/funkwhale_api/music/migrations/0022_importbatch_import_request.py
index d9f6f01d91..89fca02d62 100644
--- a/api/funkwhale_api/music/migrations/0022_importbatch_import_request.py
+++ b/api/funkwhale_api/music/migrations/0022_importbatch_import_request.py
@@ -6,15 +6,18 @@ import django.db.models.deletion
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('requests', '__first__'),
-        ('music', '0021_populate_batch_status'),
-    ]
+    dependencies = [("requests", "__first__"), ("music", "0021_populate_batch_status")]
 
     operations = [
         migrations.AddField(
-            model_name='importbatch',
-            name='import_request',
-            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='import_batches', to='requests.ImportRequest'),
-        ),
+            model_name="importbatch",
+            name="import_request",
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="import_batches",
+                to="requests.ImportRequest",
+            ),
+        )
     ]
diff --git a/api/funkwhale_api/music/migrations/0023_auto_20180407_1010.py b/api/funkwhale_api/music/migrations/0023_auto_20180407_1010.py
index ed7404ac4f..8c6537d855 100644
--- a/api/funkwhale_api/music/migrations/0023_auto_20180407_1010.py
+++ b/api/funkwhale_api/music/migrations/0023_auto_20180407_1010.py
@@ -9,79 +9,105 @@ import django.utils.timezone
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('federation', '0003_auto_20180407_1010'),
-        ('music', '0022_importbatch_import_request'),
+        ("federation", "0003_auto_20180407_1010"),
+        ("music", "0022_importbatch_import_request"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='album',
-            name='uuid',
+            model_name="album",
+            name="uuid",
             field=models.UUIDField(db_index=True, null=True, unique=True),
         ),
         migrations.AddField(
-            model_name='artist',
-            name='uuid',
+            model_name="artist",
+            name="uuid",
             field=models.UUIDField(db_index=True, null=True, unique=True),
         ),
         migrations.AddField(
-            model_name='importbatch',
-            name='uuid',
+            model_name="importbatch",
+            name="uuid",
             field=models.UUIDField(db_index=True, null=True, unique=True),
         ),
         migrations.AddField(
-            model_name='importjob',
-            name='library_track',
-            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='import_jobs', to='federation.LibraryTrack'),
+            model_name="importjob",
+            name="library_track",
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.SET_NULL,
+                related_name="import_jobs",
+                to="federation.LibraryTrack",
+            ),
         ),
         migrations.AddField(
-            model_name='importjob',
-            name='uuid',
+            model_name="importjob",
+            name="uuid",
             field=models.UUIDField(db_index=True, null=True, unique=True),
         ),
         migrations.AddField(
-            model_name='lyrics',
-            name='uuid',
+            model_name="lyrics",
+            name="uuid",
             field=models.UUIDField(db_index=True, null=True, unique=True),
         ),
         migrations.AddField(
-            model_name='track',
-            name='uuid',
+            model_name="track",
+            name="uuid",
             field=models.UUIDField(db_index=True, null=True, unique=True),
         ),
         migrations.AddField(
-            model_name='trackfile',
-            name='creation_date',
+            model_name="trackfile",
+            name="creation_date",
             field=models.DateTimeField(default=django.utils.timezone.now),
         ),
         migrations.AddField(
-            model_name='trackfile',
-            name='library_track',
-            field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='local_track_file', to='federation.LibraryTrack'),
+            model_name="trackfile",
+            name="library_track",
+            field=models.OneToOneField(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="local_track_file",
+                to="federation.LibraryTrack",
+            ),
         ),
         migrations.AddField(
-            model_name='trackfile',
-            name='modification_date',
+            model_name="trackfile",
+            name="modification_date",
             field=models.DateTimeField(auto_now=True),
         ),
         migrations.AddField(
-            model_name='trackfile',
-            name='uuid',
+            model_name="trackfile",
+            name="uuid",
             field=models.UUIDField(db_index=True, null=True, unique=True),
         ),
         migrations.AddField(
-            model_name='work',
-            name='uuid',
+            model_name="work",
+            name="uuid",
             field=models.UUIDField(db_index=True, null=True, unique=True),
         ),
         migrations.AlterField(
-            model_name='importbatch',
-            name='source',
-            field=models.CharField(choices=[('api', 'api'), ('shell', 'shell'), ('federation', 'federation')], default='api', max_length=30),
+            model_name="importbatch",
+            name="source",
+            field=models.CharField(
+                choices=[
+                    ("api", "api"),
+                    ("shell", "shell"),
+                    ("federation", "federation"),
+                ],
+                default="api",
+                max_length=30,
+            ),
         ),
         migrations.AlterField(
-            model_name='importbatch',
-            name='submitted_by',
-            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='imports', to=settings.AUTH_USER_MODEL),
+            model_name="importbatch",
+            name="submitted_by",
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="imports",
+                to=settings.AUTH_USER_MODEL,
+            ),
         ),
     ]
diff --git a/api/funkwhale_api/music/migrations/0024_populate_uuid.py b/api/funkwhale_api/music/migrations/0024_populate_uuid.py
index 10c78a3db0..349a901fc4 100644
--- a/api/funkwhale_api/music/migrations/0024_populate_uuid.py
+++ b/api/funkwhale_api/music/migrations/0024_populate_uuid.py
@@ -7,22 +7,22 @@ from django.db import migrations, models
 
 def populate_uuids(apps, schema_editor):
     models = [
-        'Album',
-        'Artist',
-        'Importbatch',
-        'Importjob',
-        'Lyrics',
-        'Track',
-        'Trackfile',
-        'Work',
+        "Album",
+        "Artist",
+        "Importbatch",
+        "Importjob",
+        "Lyrics",
+        "Track",
+        "Trackfile",
+        "Work",
     ]
     for m in models:
-        kls = apps.get_model('music', m)
-        qs = kls.objects.filter(uuid__isnull=True).only('id')
-        print('Setting uuids for {} ({} objects)'.format(m, len(qs)))
+        kls = apps.get_model("music", m)
+        qs = kls.objects.filter(uuid__isnull=True).only("id")
+        print("Setting uuids for {} ({} objects)".format(m, len(qs)))
         for o in qs:
             o.uuid = uuid.uuid4()
-            o.save(update_fields=['uuid'])
+            o.save(update_fields=["uuid"])
 
 
 def rewind(apps, schema_editor):
@@ -31,50 +31,48 @@ def rewind(apps, schema_editor):
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0023_auto_20180407_1010'),
-    ]
+    dependencies = [("music", "0023_auto_20180407_1010")]
 
     operations = [
         migrations.RunPython(populate_uuids, rewind),
         migrations.AlterField(
-            model_name='album',
-            name='uuid',
+            model_name="album",
+            name="uuid",
             field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
         ),
         migrations.AlterField(
-            model_name='artist',
-            name='uuid',
+            model_name="artist",
+            name="uuid",
             field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
         ),
         migrations.AlterField(
-            model_name='importbatch',
-            name='uuid',
+            model_name="importbatch",
+            name="uuid",
             field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
         ),
         migrations.AlterField(
-            model_name='importjob',
-            name='uuid',
+            model_name="importjob",
+            name="uuid",
             field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
         ),
         migrations.AlterField(
-            model_name='lyrics',
-            name='uuid',
+            model_name="lyrics",
+            name="uuid",
             field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
         ),
         migrations.AlterField(
-            model_name='track',
-            name='uuid',
+            model_name="track",
+            name="uuid",
             field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
         ),
         migrations.AlterField(
-            model_name='trackfile',
-            name='uuid',
+            model_name="trackfile",
+            name="uuid",
             field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
         ),
         migrations.AlterField(
-            model_name='work',
-            name='uuid',
+            model_name="work",
+            name="uuid",
             field=models.UUIDField(db_index=True, default=uuid.uuid4, unique=True),
         ),
     ]
diff --git a/api/funkwhale_api/music/migrations/0025_auto_20180419_2023.py b/api/funkwhale_api/music/migrations/0025_auto_20180419_2023.py
index 6b0230d505..be685f1fe7 100644
--- a/api/funkwhale_api/music/migrations/0025_auto_20180419_2023.py
+++ b/api/funkwhale_api/music/migrations/0025_auto_20180419_2023.py
@@ -5,14 +5,12 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0024_populate_uuid'),
-    ]
+    dependencies = [("music", "0024_populate_uuid")]
 
     operations = [
         migrations.AlterField(
-            model_name='trackfile',
-            name='source',
+            model_name="trackfile",
+            name="source",
             field=models.URLField(blank=True, max_length=500, null=True),
-        ),
+        )
     ]
diff --git a/api/funkwhale_api/music/migrations/0026_trackfile_accessed_date.py b/api/funkwhale_api/music/migrations/0026_trackfile_accessed_date.py
index 1d5327d939..f7f46f35aa 100644
--- a/api/funkwhale_api/music/migrations/0026_trackfile_accessed_date.py
+++ b/api/funkwhale_api/music/migrations/0026_trackfile_accessed_date.py
@@ -5,14 +5,12 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0025_auto_20180419_2023'),
-    ]
+    dependencies = [("music", "0025_auto_20180419_2023")]
 
     operations = [
         migrations.AddField(
-            model_name='trackfile',
-            name='accessed_date',
+            model_name="trackfile",
+            name="accessed_date",
             field=models.DateTimeField(blank=True, null=True),
-        ),
+        )
     ]
diff --git a/api/funkwhale_api/music/migrations/0027_auto_20180515_1808.py b/api/funkwhale_api/music/migrations/0027_auto_20180515_1808.py
index 835e115a65..1e3949da48 100644
--- a/api/funkwhale_api/music/migrations/0027_auto_20180515_1808.py
+++ b/api/funkwhale_api/music/migrations/0027_auto_20180515_1808.py
@@ -6,24 +6,28 @@ import taggit.managers
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('music', '0026_trackfile_accessed_date'),
-    ]
+    dependencies = [("music", "0026_trackfile_accessed_date")]
 
     operations = [
         migrations.AddField(
-            model_name='trackfile',
-            name='bitrate',
+            model_name="trackfile",
+            name="bitrate",
             field=models.IntegerField(blank=True, null=True),
         ),
         migrations.AddField(
-            model_name='trackfile',
-            name='size',
+            model_name="trackfile",
+            name="size",
             field=models.IntegerField(blank=True, null=True),
         ),
         migrations.AlterField(
-            model_name='track',
-            name='tags',
-            field=taggit.managers.TaggableManager(blank=True, help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
+            model_name="track",
+            name="tags",
+            field=taggit.managers.TaggableManager(
+                blank=True,
+                help_text="A comma-separated list of tags.",
+                through="taggit.TaggedItem",
+                to="taggit.Tag",
+                verbose_name="Tags",
+            ),
         ),
     ]
diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py
index bf3f9e12c2..d4625a435f 100644
--- a/api/funkwhale_api/music/models.py
+++ b/api/funkwhale_api/music/models.py
@@ -29,15 +29,14 @@ from . import utils
 
 class APIModelMixin(models.Model):
     mbid = models.UUIDField(unique=True, db_index=True, null=True, blank=True)
-    uuid = models.UUIDField(
-        unique=True, db_index=True, default=uuid.uuid4)
+    uuid = models.UUIDField(unique=True, db_index=True, default=uuid.uuid4)
     api_includes = []
     creation_date = models.DateTimeField(default=timezone.now)
     import_hooks = []
 
     class Meta:
         abstract = True
-        ordering = ['-creation_date']
+        ordering = ["-creation_date"]
 
     @classmethod
     def get_or_create_from_api(cls, mbid):
@@ -47,14 +46,20 @@ class APIModelMixin(models.Model):
             return cls.create_from_api(id=mbid), True
 
     def get_api_data(self):
-        return self.__class__.api.get(id=self.mbid, includes=self.api_includes)[self.musicbrainz_model]
+        return self.__class__.api.get(id=self.mbid, includes=self.api_includes)[
+            self.musicbrainz_model
+        ]
 
     @classmethod
     def create_from_api(cls, **kwargs):
-        if kwargs.get('id'):
-            raw_data = cls.api.get(id=kwargs['id'], includes=cls.api_includes)[cls.musicbrainz_model]
+        if kwargs.get("id"):
+            raw_data = cls.api.get(id=kwargs["id"], includes=cls.api_includes)[
+                cls.musicbrainz_model
+            ]
         else:
-            raw_data = cls.api.search(**kwargs)['{0}-list'.format(cls.musicbrainz_model)][0]
+            raw_data = cls.api.search(**kwargs)[
+                "{0}-list".format(cls.musicbrainz_model)
+            ][0]
         cleaned_data = cls.clean_musicbrainz_data(raw_data)
         return importers.load(cls, cleaned_data, raw_data, cls.import_hooks)
 
@@ -73,32 +78,28 @@ class APIModelMixin(models.Model):
     @property
     def musicbrainz_url(self):
         if self.mbid:
-            return 'https://musicbrainz.org/{}/{}'.format(
-                self.musicbrainz_model, self.mbid)
+            return "https://musicbrainz.org/{}/{}".format(
+                self.musicbrainz_model, self.mbid
+            )
 
 
 class ArtistQuerySet(models.QuerySet):
     def with_albums_count(self):
-        return self.annotate(_albums_count=models.Count('albums'))
+        return self.annotate(_albums_count=models.Count("albums"))
 
     def with_albums(self):
         return self.prefetch_related(
-            models.Prefetch(
-                'albums', queryset=Album.objects.with_tracks_count())
+            models.Prefetch("albums", queryset=Album.objects.with_tracks_count())
         )
 
 
 class Artist(APIModelMixin):
     name = models.CharField(max_length=255)
 
-    musicbrainz_model = 'artist'
+    musicbrainz_model = "artist"
     musicbrainz_mapping = {
-        'mbid': {
-            'musicbrainz_field_name': 'id'
-        },
-        'name': {
-            'musicbrainz_field_name': 'name'
-        }
+        "mbid": {"musicbrainz_field_name": "id"},
+        "name": {"musicbrainz_field_name": "name"},
     }
     api = musicbrainz.api.artists
     objects = ArtistQuerySet.as_manager()
@@ -116,14 +117,12 @@ class Artist(APIModelMixin):
 
     @classmethod
     def get_or_create_from_name(cls, name, **kwargs):
-        kwargs.update({'name': name})
-        return cls.objects.get_or_create(
-            name__iexact=name,
-            defaults=kwargs)
+        kwargs.update({"name": name})
+        return cls.objects.get_or_create(name__iexact=name, defaults=kwargs)
 
 
 def import_artist(v):
-    a = Artist.get_or_create_from_api(mbid=v[0]['artist']['id'])[0]
+    a = Artist.get_or_create_from_api(mbid=v[0]["artist"]["id"])[0]
     return a
 
 
@@ -135,78 +134,64 @@ def parse_date(v):
 
 
 def import_tracks(instance, cleaned_data, raw_data):
-    for track_data in raw_data['medium-list'][0]['track-list']:
-        track_cleaned_data = Track.clean_musicbrainz_data(track_data['recording'])
-        track_cleaned_data['album'] = instance
-        track_cleaned_data['position'] = int(track_data['position'])
-        track = importers.load(Track, track_cleaned_data, track_data, Track.import_hooks)
+    for track_data in raw_data["medium-list"][0]["track-list"]:
+        track_cleaned_data = Track.clean_musicbrainz_data(track_data["recording"])
+        track_cleaned_data["album"] = instance
+        track_cleaned_data["position"] = int(track_data["position"])
+        track = importers.load(
+            Track, track_cleaned_data, track_data, Track.import_hooks
+        )
 
 
 class AlbumQuerySet(models.QuerySet):
     def with_tracks_count(self):
-        return self.annotate(_tracks_count=models.Count('tracks'))
+        return self.annotate(_tracks_count=models.Count("tracks"))
 
 
 class Album(APIModelMixin):
     title = models.CharField(max_length=255)
-    artist = models.ForeignKey(
-        Artist, related_name='albums', on_delete=models.CASCADE)
+    artist = models.ForeignKey(Artist, related_name="albums", on_delete=models.CASCADE)
     release_date = models.DateField(null=True)
     release_group_id = models.UUIDField(null=True, blank=True)
-    cover = VersatileImageField(upload_to='albums/covers/%Y/%m/%d', null=True, blank=True)
-    TYPE_CHOICES = (
-        ('album', 'Album'),
+    cover = VersatileImageField(
+        upload_to="albums/covers/%Y/%m/%d", null=True, blank=True
     )
-    type = models.CharField(choices=TYPE_CHOICES, max_length=30, default='album')
+    TYPE_CHOICES = (("album", "Album"),)
+    type = models.CharField(choices=TYPE_CHOICES, max_length=30, default="album")
 
-    api_includes = ['artist-credits', 'recordings', 'media', 'release-groups']
+    api_includes = ["artist-credits", "recordings", "media", "release-groups"]
     api = musicbrainz.api.releases
-    musicbrainz_model = 'release'
+    musicbrainz_model = "release"
     musicbrainz_mapping = {
-        'mbid': {
-            'musicbrainz_field_name': 'id',
+        "mbid": {"musicbrainz_field_name": "id"},
+        "position": {
+            "musicbrainz_field_name": "release-list",
+            "converter": lambda v: int(v[0]["medium-list"][0]["position"]),
         },
-        'position': {
-            'musicbrainz_field_name': 'release-list',
-            'converter': lambda v: int(v[0]['medium-list'][0]['position']),
+        "release_group_id": {
+            "musicbrainz_field_name": "release-group",
+            "converter": lambda v: v["id"],
         },
-        'release_group_id': {
-            'musicbrainz_field_name': 'release-group',
-            'converter': lambda v: v['id'],
-        },
-        'title': {
-            'musicbrainz_field_name': 'title',
-        },
-        'release_date': {
-            'musicbrainz_field_name': 'date',
-            'converter': parse_date,
-
+        "title": {"musicbrainz_field_name": "title"},
+        "release_date": {"musicbrainz_field_name": "date", "converter": parse_date},
+        "type": {"musicbrainz_field_name": "type", "converter": lambda v: v.lower()},
+        "artist": {
+            "musicbrainz_field_name": "artist-credit",
+            "converter": import_artist,
         },
-        'type': {
-            'musicbrainz_field_name': 'type',
-            'converter': lambda v: v.lower(),
-        },
-        'artist': {
-            'musicbrainz_field_name': 'artist-credit',
-            'converter': import_artist,
-        }
     }
     objects = AlbumQuerySet.as_manager()
 
     def get_image(self, data=None):
         if data:
-            f = ContentFile(data['content'])
-            extensions = {
-                'image/jpeg': 'jpg',
-                'image/png': 'png',
-                'image/gif': 'gif',
-            }
-            extension = extensions.get(data['mimetype'], 'jpg')
-            self.cover.save('{}.{}'.format(self.uuid, extension), f)
+            f = ContentFile(data["content"])
+            extensions = {"image/jpeg": "jpg", "image/png": "png", "image/gif": "gif"}
+            extension = extensions.get(data["mimetype"], "jpg")
+            self.cover.save("{}.{}".format(self.uuid, extension), f)
         else:
-            image_data =  musicbrainz.api.images.get_front(str(self.mbid))
+            image_data = musicbrainz.api.images.get_front(str(self.mbid))
             f = ContentFile(image_data)
-            self.cover.save('{0}.jpg'.format(self.mbid), f)
+            self.cover.save("{0}.jpg".format(self.mbid), f)
         return self.cover.file
 
     def __str__(self):
@@ -222,35 +207,30 @@ class Album(APIModelMixin):
 
     @classmethod
     def get_or_create_from_title(cls, title, **kwargs):
-        kwargs.update({'title': title})
-        return cls.objects.get_or_create(
-            title__iexact=title,
-            defaults=kwargs)
+        kwargs.update({"title": title})
+        return cls.objects.get_or_create(title__iexact=title, defaults=kwargs)
 
 
 def import_tags(instance, cleaned_data, raw_data):
     MINIMUM_COUNT = 2
     tags_to_add = []
-    for tag_data in raw_data.get('tag-list', []):
+    for tag_data in raw_data.get("tag-list", []):
         try:
-            if int(tag_data['count']) < MINIMUM_COUNT:
+            if int(tag_data["count"]) < MINIMUM_COUNT:
                 continue
         except ValueError:
             continue
-        tags_to_add.append(tag_data['name'])
+        tags_to_add.append(tag_data["name"])
     instance.tags.add(*tags_to_add)
 
 
 def import_album(v):
-    a = Album.get_or_create_from_api(mbid=v[0]['id'])[0]
+    a = Album.get_or_create_from_api(mbid=v[0]["id"])[0]
     return a
 
 
 def link_recordings(instance, cleaned_data, raw_data):
-    tracks = [
-        r['target']
-        for r in raw_data['recording-relation-list']
-    ]
+    tracks = [r["target"] for r in raw_data["recording-relation-list"]]
     Track.objects.filter(mbid__in=tracks).update(work=instance)
 
 
@@ -258,9 +238,9 @@ def import_lyrics(instance, cleaned_data, raw_data):
     try:
         url = [
             url_data
-            for url_data in raw_data['url-relation-list']
-            if url_data['type'] == 'lyrics'
-        ][0]['target']
+            for url_data in raw_data["url-relation-list"]
+            if url_data["type"] == "lyrics"
+        ][0]["target"]
     except (IndexError, KeyError):
         return
     l, _ = Lyrics.objects.get_or_create(work=instance, url=url)
@@ -274,47 +254,31 @@ class Work(APIModelMixin):
     title = models.CharField(max_length=255)
 
     api = musicbrainz.api.works
-    api_includes = ['url-rels', 'recording-rels']
-    musicbrainz_model = 'work'
+    api_includes = ["url-rels", "recording-rels"]
+    musicbrainz_model = "work"
     musicbrainz_mapping = {
-        'mbid': {
-            'musicbrainz_field_name': 'id'
-        },
-        'title': {
-            'musicbrainz_field_name': 'title'
-        },
-        'language': {
-            'musicbrainz_field_name': 'language',
-        },
-        'nature': {
-            'musicbrainz_field_name': 'type',
-            'converter': lambda v: v.lower(),
-        },
+        "mbid": {"musicbrainz_field_name": "id"},
+        "title": {"musicbrainz_field_name": "title"},
+        "language": {"musicbrainz_field_name": "language"},
+        "nature": {"musicbrainz_field_name": "type", "converter": lambda v: v.lower()},
     }
-    import_hooks = [
-        import_lyrics,
-        link_recordings
-    ]
+    import_hooks = [import_lyrics, link_recordings]
 
     def fetch_lyrics(self):
         l = self.lyrics.first()
         if l:
             return l
-        data = self.api.get(self.mbid, includes=['url-rels'])['work']
+        data = self.api.get(self.mbid, includes=["url-rels"])["work"]
         l = import_lyrics(self, {}, data)
 
         return l
 
 
 class Lyrics(models.Model):
-    uuid = models.UUIDField(
-        unique=True, db_index=True, default=uuid.uuid4)
+    uuid = models.UUIDField(unique=True, db_index=True, default=uuid.uuid4)
     work = models.ForeignKey(
-        Work,
-        related_name='lyrics',
-        null=True,
-        blank=True,
-        on_delete=models.CASCADE)
+        Work, related_name="lyrics", null=True, blank=True, on_delete=models.CASCADE
+    )
     url = models.URLField(unique=True)
     content = models.TextField(null=True, blank=True)
 
@@ -324,67 +288,55 @@ class Lyrics(models.Model):
             self.content,
             safe_mode=True,
             enable_attributes=False,
-            extensions=['markdown.extensions.nl2br'])
+            extensions=["markdown.extensions.nl2br"],
+        )
 
 
 class TrackQuerySet(models.QuerySet):
     def for_nested_serialization(self):
-        return (self.select_related()
-                    .select_related('album__artist', 'artist')
-                    .prefetch_related('files'))
+        return (
+            self.select_related()
+            .select_related("album__artist", "artist")
+            .prefetch_related("files")
+        )
 
 
 def get_artist(release_list):
     return Artist.get_or_create_from_api(
-        mbid=release_list[0]['artist-credits'][0]['artists']['id'])[0]
+        mbid=release_list[0]["artist-credits"][0]["artists"]["id"]
+    )[0]
 
 
 class Track(APIModelMixin):
     title = models.CharField(max_length=255)
-    artist = models.ForeignKey(
-        Artist, related_name='tracks', on_delete=models.CASCADE)
+    artist = models.ForeignKey(Artist, related_name="tracks", on_delete=models.CASCADE)
     position = models.PositiveIntegerField(null=True, blank=True)
     album = models.ForeignKey(
-        Album,
-        related_name='tracks',
-        null=True,
-        blank=True,
-        on_delete=models.CASCADE)
+        Album, related_name="tracks", null=True, blank=True, on_delete=models.CASCADE
+    )
     work = models.ForeignKey(
-        Work,
-        related_name='tracks',
-        null=True,
-        blank=True,
-        on_delete=models.CASCADE)
+        Work, related_name="tracks", null=True, blank=True, on_delete=models.CASCADE
+    )
 
-    musicbrainz_model = 'recording'
+    musicbrainz_model = "recording"
     api = musicbrainz.api.recordings
-    api_includes = ['artist-credits', 'releases', 'media', 'tags', 'work-rels']
+    api_includes = ["artist-credits", "releases", "media", "tags", "work-rels"]
     musicbrainz_mapping = {
-        'mbid': {
-            'musicbrainz_field_name': 'id'
-        },
-        'title': {
-            'musicbrainz_field_name': 'title'
-        },
-        'artist': {
+        "mbid": {"musicbrainz_field_name": "id"},
+        "title": {"musicbrainz_field_name": "title"},
+        "artist": {
             # we use the artist from the release to avoid #237
-            'musicbrainz_field_name': 'release-list',
-            'converter': get_artist,
-        },
-        'album': {
-            'musicbrainz_field_name': 'release-list',
-            'converter': import_album,
+            "musicbrainz_field_name": "release-list",
+            "converter": get_artist,
         },
+        "album": {"musicbrainz_field_name": "release-list", "converter": import_album},
     }
-    import_hooks = [
-        import_tags
-    ]
+    import_hooks = [import_tags]
     objects = TrackQuerySet.as_manager()
     tags = TaggableManager(blank=True)
 
     class Meta:
-        ordering = ['album', 'position']
+        ordering = ["album", "position"]
 
     def __str__(self):
         return self.title
@@ -399,43 +351,33 @@ class Track(APIModelMixin):
     def get_work(self):
         if self.work:
             return self.work
-        data = self.api.get(self.mbid, includes=['work-rels'])
+        data = self.api.get(self.mbid, includes=["work-rels"])
         try:
-            work_data = data['recording']['work-relation-list'][0]['work']
+            work_data = data["recording"]["work-relation-list"][0]["work"]
         except (IndexError, KeyError):
             return
-        work, _ = Work.get_or_create_from_api(mbid=work_data['id'])
+        work, _ = Work.get_or_create_from_api(mbid=work_data["id"])
         return work
 
     def get_lyrics_url(self):
-        return reverse('api:v1:tracks-lyrics', kwargs={'pk': self.pk})
+        return reverse("api:v1:tracks-lyrics", kwargs={"pk": self.pk})
 
     @property
     def full_name(self):
         try:
-            return '{} - {} - {}'.format(
-                self.artist.name,
-                self.album.title,
-                self.title,
-            )
+            return "{} - {} - {}".format(self.artist.name, self.album.title, self.title)
         except AttributeError:
-            return '{} - {}'.format(
-                self.artist.name,
-                self.title,
-            )
+            return "{} - {}".format(self.artist.name, self.title)
 
     def get_activity_url(self):
         if self.mbid:
-            return 'https://musicbrainz.org/recording/{}'.format(
-                self.mbid)
-        return settings.FUNKWHALE_URL + '/tracks/{}'.format(self.pk)
+            return "https://musicbrainz.org/recording/{}".format(self.mbid)
+        return settings.FUNKWHALE_URL + "/tracks/{}".format(self.pk)
 
     @classmethod
     def get_or_create_from_title(cls, title, **kwargs):
-        kwargs.update({'title': title})
-        return cls.objects.get_or_create(
-            title__iexact=title,
-            defaults=kwargs)
+        kwargs.update({"title": title})
+        return cls.objects.get_or_create(title__iexact=title, defaults=kwargs)
 
     @classmethod
     def get_or_create_from_release(cls, release_mbid, mbid):
@@ -448,35 +390,32 @@ class Track(APIModelMixin):
 
         album = Album.get_or_create_from_api(release_mbid)[0]
         data = musicbrainz.client.api.releases.get(
-            str(album.mbid), includes=Album.api_includes)
-        tracks = [
-            t
-            for m in data['release']['medium-list']
-            for t in m['track-list']
-        ]
+            str(album.mbid), includes=Album.api_includes
+        )
+        tracks = [t for m in data["release"]["medium-list"] for t in m["track-list"]]
         track_data = None
         for track in tracks:
-            if track['recording']['id'] == mbid:
+            if track["recording"]["id"] == mbid:
                 track_data = track
                 break
         if not track_data:
-            raise ValueError('No track found matching this ID')
+            raise ValueError("No track found matching this ID")
 
         return cls.objects.update_or_create(
             mbid=mbid,
             defaults={
-                'position': int(track['position']),
-                'title': track['recording']['title'],
-                'album': album,
-                'artist': album.artist,
-            }
+                "position": int(track["position"]),
+                "title": track["recording"]["title"],
+                "album": album,
+                "artist": album.artist,
+            },
         )
+
+
 class TrackFile(models.Model):
-    uuid = models.UUIDField(
-        unique=True, db_index=True, default=uuid.uuid4)
-    track = models.ForeignKey(
-        Track, related_name='files', on_delete=models.CASCADE)
-    audio_file = models.FileField(upload_to='tracks/%Y/%m/%d', max_length=255)
+    uuid = models.UUIDField(unique=True, db_index=True, default=uuid.uuid4)
+    track = models.ForeignKey(Track, related_name="files", on_delete=models.CASCADE)
+    audio_file = models.FileField(upload_to="tracks/%Y/%m/%d", max_length=255)
     source = models.URLField(null=True, blank=True, max_length=500)
     creation_date = models.DateTimeField(default=timezone.now)
     modification_date = models.DateTimeField(auto_now=True)
@@ -488,8 +427,8 @@ class TrackFile(models.Model):
     mimetype = models.CharField(null=True, blank=True, max_length=200)
 
     library_track = models.OneToOneField(
-        'federation.LibraryTrack',
-        related_name='local_track_file',
+        "federation.LibraryTrack",
+        related_name="local_track_file",
         on_delete=models.CASCADE,
         null=True,
         blank=True,
@@ -499,45 +438,38 @@ class TrackFile(models.Model):
         # import the track file, since there is not any
         # we create a tmp dir for the download
         tmp_dir = tempfile.mkdtemp()
-        data = downloader.download(
-            self.source,
-            target_directory=tmp_dir)
-        self.duration = data.get('duration', None)
+        data = downloader.download(self.source, target_directory=tmp_dir)
+        self.duration = data.get("duration", None)
         self.audio_file.save(
-            os.path.basename(data['audio_file_path']),
-            File(open(data['audio_file_path'], 'rb'))
+            os.path.basename(data["audio_file_path"]),
+            File(open(data["audio_file_path"], "rb")),
         )
         shutil.rmtree(tmp_dir)
         return self.audio_file
 
     def get_federation_url(self):
-        return federation_utils.full_url(
-            '/federation/music/file/{}'.format(self.uuid)
-        )
+        return federation_utils.full_url("/federation/music/file/{}".format(self.uuid))
 
     @property
     def path(self):
-        return reverse(
-            'api:v1:trackfiles-serve', kwargs={'pk': self.pk})
+        return reverse("api:v1:trackfiles-serve", kwargs={"pk": self.pk})
 
     @property
     def filename(self):
-        return '{}.{}'.format(
-            self.track.full_name,
-            self.extension)
+        return "{}.{}".format(self.track.full_name, self.extension)
 
     @property
     def extension(self):
         if not self.audio_file:
             return
-        return os.path.splitext(self.audio_file.name)[-1].replace('.', '', 1)
+        return os.path.splitext(self.audio_file.name)[-1].replace(".", "", 1)
 
     def get_file_size(self):
         if self.audio_file:
             return self.audio_file.size
 
-        if self.source.startswith('file://'):
-            return os.path.getsize(self.source.replace('file://', '', 1))
+        if self.source.startswith("file://"):
+            return os.path.getsize(self.source.replace("file://", "", 1))
 
         if self.library_track and self.library_track.audio_file:
             return self.library_track.audio_file.size
@@ -545,8 +477,8 @@ class TrackFile(models.Model):
     def get_audio_file(self):
         if self.audio_file:
             return self.audio_file.open()
-        if self.source.startswith('file://'):
-            return open(self.source.replace('file://', '', 1), 'rb')
+        if self.source.startswith("file://"):
+            return open(self.source.replace("file://", "", 1), "rb")
         if self.library_track and self.library_track.audio_file:
             return self.library_track.audio_file.open()
 
@@ -557,15 +489,15 @@ class TrackFile(models.Model):
                 audio_data = utils.get_audio_file_data(f)
             if not audio_data:
                 return
-            self.duration = int(audio_data['length'])
-            self.bitrate = audio_data['bitrate']
+            self.duration = int(audio_data["length"])
+            self.bitrate = audio_data["bitrate"]
             self.size = self.get_file_size()
         else:
             lt = self.library_track
             if lt:
-                self.duration = lt.get_metadata('length')
-                self.size = lt.get_metadata('size')
-                self.bitrate = lt.get_metadata('bitrate')
+                self.duration = lt.get_metadata("length")
+                self.size = lt.get_metadata("size")
+                self.bitrate = lt.get_metadata("bitrate")
 
     def save(self, **kwargs):
         if not self.mimetype and self.audio_file:
@@ -580,41 +512,44 @@ class TrackFile(models.Model):
 
 
 IMPORT_STATUS_CHOICES = (
-    ('pending', 'Pending'),
-    ('finished', 'Finished'),
-    ('errored', 'Errored'),
-    ('skipped', 'Skipped'),
+    ("pending", "Pending"),
+    ("finished", "Finished"),
+    ("errored", "Errored"),
+    ("skipped", "Skipped"),
 )
 
 
 class ImportBatch(models.Model):
-    uuid = models.UUIDField(
-        unique=True, db_index=True, default=uuid.uuid4)
+    uuid = models.UUIDField(unique=True, db_index=True, default=uuid.uuid4)
     IMPORT_BATCH_SOURCES = [
-        ('api', 'api'),
-        ('shell', 'shell'),
-        ('federation', 'federation'),
+        ("api", "api"),
+        ("shell", "shell"),
+        ("federation", "federation"),
     ]
     source = models.CharField(
-        max_length=30, default='api', choices=IMPORT_BATCH_SOURCES)
+        max_length=30, default="api", choices=IMPORT_BATCH_SOURCES
+    )
     creation_date = models.DateTimeField(default=timezone.now)
     submitted_by = models.ForeignKey(
-        'users.User',
-        related_name='imports',
+        "users.User",
+        related_name="imports",
         null=True,
         blank=True,
-        on_delete=models.CASCADE)
+        on_delete=models.CASCADE,
+    )
     status = models.CharField(
-        choices=IMPORT_STATUS_CHOICES, default='pending', max_length=30)
+        choices=IMPORT_STATUS_CHOICES, default="pending", max_length=30
+    )
     import_request = models.ForeignKey(
-        'requests.ImportRequest',
-        related_name='import_batches',
+        "requests.ImportRequest",
+        related_name="import_batches",
         null=True,
         blank=True,
-        on_delete=models.CASCADE)
+        on_delete=models.CASCADE,
+    )
 
     class Meta:
-        ordering = ['-creation_date']
+        ordering = ["-creation_date"]
 
     def __str__(self):
         return str(self.pk)
@@ -624,46 +559,46 @@ class ImportBatch(models.Model):
         self.status = utils.compute_status(self.jobs.all())
         if self.status == old_status:
             return
-        self.save(update_fields=['status'])
-        if self.status != old_status and self.status == 'finished':
+        self.save(update_fields=["status"])
+        if self.status != old_status and self.status == "finished":
             from . import tasks
+
             tasks.import_batch_notify_followers.delay(import_batch_id=self.pk)
 
     def get_federation_url(self):
         return federation_utils.full_url(
-            '/federation/music/import/batch/{}'.format(self.uuid)
+            "/federation/music/import/batch/{}".format(self.uuid)
         )
 
 
 class ImportJob(models.Model):
-    uuid = models.UUIDField(
-        unique=True, db_index=True, default=uuid.uuid4)
+    uuid = models.UUIDField(unique=True, db_index=True, default=uuid.uuid4)
     batch = models.ForeignKey(
-        ImportBatch, related_name='jobs', on_delete=models.CASCADE)
+        ImportBatch, related_name="jobs", on_delete=models.CASCADE
+    )
     track_file = models.ForeignKey(
-        TrackFile,
-        related_name='jobs',
-        null=True,
-        blank=True,
-        on_delete=models.CASCADE)
+        TrackFile, related_name="jobs", null=True, blank=True, on_delete=models.CASCADE
+    )
     source = models.CharField(max_length=500)
     mbid = models.UUIDField(editable=False, null=True, blank=True)
 
     status = models.CharField(
-        choices=IMPORT_STATUS_CHOICES, default='pending', max_length=30)
+        choices=IMPORT_STATUS_CHOICES, default="pending", max_length=30
+    )
     audio_file = models.FileField(
-        upload_to='imports/%Y/%m/%d', max_length=255, null=True, blank=True)
+        upload_to="imports/%Y/%m/%d", max_length=255, null=True, blank=True
+    )
 
     library_track = models.ForeignKey(
-        'federation.LibraryTrack',
-        related_name='import_jobs',
+        "federation.LibraryTrack",
+        related_name="import_jobs",
         on_delete=models.SET_NULL,
         null=True,
-        blank=True
+        blank=True,
     )
 
     class Meta:
-        ordering = ('id', )
+        ordering = ("id",)
 
 
 @receiver(post_save, sender=ImportJob)
@@ -673,22 +608,22 @@ def update_batch_status(sender, instance, **kwargs):
 
 @receiver(post_save, sender=ImportBatch)
 def update_request_status(sender, instance, created, **kwargs):
-    update_fields = kwargs.get('update_fields', []) or []
+    update_fields = kwargs.get("update_fields", []) or []
     if not instance.import_request:
         return
 
-    if not created and not 'status' in update_fields:
+    if not created and not "status" in update_fields:
         return
 
     r_status = instance.import_request.status
     status = instance.status
 
-    if status == 'pending' and r_status == 'pending':
+    if status == "pending" and r_status == "pending":
         # let's mark the request as accepted since we started an import
-        instance.import_request.status = 'accepted'
-        return instance.import_request.save(update_fields=['status'])
+        instance.import_request.status = "accepted"
+        return instance.import_request.save(update_fields=["status"])
 
-    if status == 'finished' and r_status == 'accepted':
+    if status == "finished" and r_status == "accepted":
         # let's mark the request as imported since the import is over
-        instance.import_request.status = 'imported'
-        return instance.import_request.save(update_fields=['status'])
+        instance.import_request.status = "imported"
+        return instance.import_request.save(update_fields=["status"])
diff --git a/api/funkwhale_api/music/permissions.py b/api/funkwhale_api/music/permissions.py
index d31e1c5d50..a85e5a9a6e 100644
--- a/api/funkwhale_api/music/permissions.py
+++ b/api/funkwhale_api/music/permissions.py
@@ -8,22 +8,19 @@ from funkwhale_api.federation import models
 
 
 class Listen(BasePermission):
-
     def has_permission(self, request, view):
-        if not preferences.get('common__api_authentication_required'):
+        if not preferences.get("common__api_authentication_required"):
             return True
 
-        user = getattr(request, 'user', None)
+        user = getattr(request, "user", None)
         if user and user.is_authenticated:
             return True
 
-        actor = getattr(request, 'actor', None)
+        actor = getattr(request, "actor", None)
         if actor is None:
             return False
 
-        library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
+        library = actors.SYSTEM_ACTORS["library"].get_actor_instance()
         return models.Follow.objects.filter(
-            target=library,
-            actor=actor,
-            approved=True
+            target=library, actor=actor, approved=True
         ).exists()
diff --git a/api/funkwhale_api/music/serializers.py b/api/funkwhale_api/music/serializers.py
index b72bb8c4a6..7aa8603e2c 100644
--- a/api/funkwhale_api/music/serializers.py
+++ b/api/funkwhale_api/music/serializers.py
@@ -19,14 +19,14 @@ class ArtistAlbumSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Album
         fields = (
-            'id',
-            'mbid',
-            'title',
-            'artist',
-            'release_date',
-            'cover',
-            'creation_date',
-            'tracks_count',
+            "id",
+            "mbid",
+            "title",
+            "artist",
+            "release_date",
+            "cover",
+            "creation_date",
+            "tracks_count",
         )
 
     def get_tracks_count(self, o):
@@ -38,13 +38,7 @@ class ArtistWithAlbumsSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = models.Artist
-        fields = (
-            'id',
-            'mbid',
-            'name',
-            'creation_date',
-            'albums',
-        )
+        fields = ("id", "mbid", "name", "creation_date", "albums")
 
 
 class TrackFileSerializer(serializers.ModelSerializer):
@@ -53,23 +47,18 @@ class TrackFileSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.TrackFile
         fields = (
-            'id',
-            'path',
-            'source',
-            'filename',
-            'mimetype',
-            'track',
-            'duration',
-            'mimetype',
-            'bitrate',
-            'size',
+            "id",
+            "path",
+            "source",
+            "filename",
+            "mimetype",
+            "track",
+            "duration",
+            "mimetype",
+            "bitrate",
+            "size",
         )
-        read_only_fields = [
-            'duration',
-            'mimetype',
-            'bitrate',
-            'size',
-        ]
+        read_only_fields = ["duration", "mimetype", "bitrate", "size"]
 
     def get_path(self, o):
         url = o.path
@@ -82,26 +71,21 @@ class AlbumTrackSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Track
         fields = (
-            'id',
-            'mbid',
-            'title',
-            'album',
-            'artist',
-            'creation_date',
-            'files',
-            'position',
+            "id",
+            "mbid",
+            "title",
+            "album",
+            "artist",
+            "creation_date",
+            "files",
+            "position",
         )
 
 
 class ArtistSimpleSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Artist
-        fields = (
-            'id',
-            'mbid',
-            'name',
-            'creation_date',
-        )
+        fields = ("id", "mbid", "name", "creation_date")
 
 
 class AlbumSerializer(serializers.ModelSerializer):
@@ -111,20 +95,20 @@ class AlbumSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Album
         fields = (
-            'id',
-            'mbid',
-            'title',
-            'artist',
-            'tracks',
-            'release_date',
-            'cover',
-            'creation_date',
+            "id",
+            "mbid",
+            "title",
+            "artist",
+            "tracks",
+            "release_date",
+            "cover",
+            "creation_date",
         )
 
     def get_tracks(self, o):
         ordered_tracks = sorted(
             o.tracks.all(),
-            key=lambda v: (v.position, v.title) if v.position else (99999, v.title)
+            key=lambda v: (v.position, v.title) if v.position else (99999, v.title),
         )
         return AlbumTrackSerializer(ordered_tracks, many=True).data
 
@@ -135,13 +119,13 @@ class TrackAlbumSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Album
         fields = (
-            'id',
-            'mbid',
-            'title',
-            'artist',
-            'release_date',
-            'cover',
-            'creation_date',
+            "id",
+            "mbid",
+            "title",
+            "artist",
+            "release_date",
+            "cover",
+            "creation_date",
         )
 
 
@@ -154,15 +138,15 @@ class TrackSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Track
         fields = (
-            'id',
-            'mbid',
-            'title',
-            'album',
-            'artist',
-            'creation_date',
-            'files',
-            'position',
-            'lyrics',
+            "id",
+            "mbid",
+            "title",
+            "album",
+            "artist",
+            "creation_date",
+            "files",
+            "position",
+            "lyrics",
         )
 
     def get_lyrics(self, obj):
@@ -172,20 +156,19 @@ class TrackSerializer(serializers.ModelSerializer):
 class TagSerializer(serializers.ModelSerializer):
     class Meta:
         model = Tag
-        fields = ('id', 'name', 'slug')
+        fields = ("id", "name", "slug")
 
 
 class SimpleAlbumSerializer(serializers.ModelSerializer):
-
     class Meta:
         model = models.Album
-        fields = ('id', 'mbid', 'title', 'release_date', 'cover')
+        fields = ("id", "mbid", "title", "release_date", "cover")
 
 
 class LyricsSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Lyrics
-        fields = ('id', 'work', 'content', 'content_rendered')
+        fields = ("id", "work", "content", "content_rendered")
 
 
 class ImportJobSerializer(serializers.ModelSerializer):
@@ -193,15 +176,8 @@ class ImportJobSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = models.ImportJob
-        fields = (
-            'id',
-            'mbid',
-            'batch',
-            'source',
-            'status',
-            'track_file',
-            'audio_file')
-        read_only_fields = ('status', 'track_file')
+        fields = ("id", "mbid", "batch", "source", "status", "track_file", "audio_file")
+        read_only_fields = ("status", "track_file")
 
 
 class ImportBatchSerializer(serializers.ModelSerializer):
@@ -210,19 +186,19 @@ class ImportBatchSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.ImportBatch
         fields = (
-            'id',
-            'submitted_by',
-            'source',
-            'status',
-            'creation_date',
-            'import_request')
-        read_only_fields = (
-            'creation_date', 'submitted_by', 'source')
+            "id",
+            "submitted_by",
+            "source",
+            "status",
+            "creation_date",
+            "import_request",
+        )
+        read_only_fields = ("creation_date", "submitted_by", "source")
 
     def to_representation(self, instance):
         repr = super().to_representation(instance)
         try:
-            repr['job_count'] = instance.job_count
+            repr["job_count"] = instance.job_count
         except AttributeError:
             # Queryset was not annotated
             pass
@@ -231,50 +207,43 @@ class ImportBatchSerializer(serializers.ModelSerializer):
 
 class TrackActivitySerializer(activity_serializers.ModelSerializer):
     type = serializers.SerializerMethodField()
-    name = serializers.CharField(source='title')
-    artist = serializers.CharField(source='artist.name')
-    album = serializers.CharField(source='album.title')
+    name = serializers.CharField(source="title")
+    artist = serializers.CharField(source="artist.name")
+    album = serializers.CharField(source="album.title")
 
     class Meta:
         model = models.Track
-        fields = [
-            'id',
-            'local_id',
-            'name',
-            'type',
-            'artist',
-            'album',
-        ]
+        fields = ["id", "local_id", "name", "type", "artist", "album"]
 
     def get_type(self, obj):
-        return 'Audio'
+        return "Audio"
 
 
 class ImportJobRunSerializer(serializers.Serializer):
     jobs = serializers.PrimaryKeyRelatedField(
         many=True,
-        queryset=models.ImportJob.objects.filter(
-            status__in=['pending', 'errored']
-        )
+        queryset=models.ImportJob.objects.filter(status__in=["pending", "errored"]),
     )
     batches = serializers.PrimaryKeyRelatedField(
-        many=True,
-        queryset=models.ImportBatch.objects.all()
+        many=True, queryset=models.ImportBatch.objects.all()
     )
 
     def validate(self, validated_data):
-        jobs = validated_data['jobs']
-        batches_ids = [b.pk for b in validated_data['batches']]
+        jobs = validated_data["jobs"]
+        batches_ids = [b.pk for b in validated_data["batches"]]
         query = Q(batch__pk__in=batches_ids)
         query |= Q(pk__in=[j.id for j in jobs])
-        queryset = models.ImportJob.objects.filter(query).filter(
-            status__in=['pending', 'errored']).distinct()
-        validated_data['_jobs'] = queryset
+        queryset = (
+            models.ImportJob.objects.filter(query)
+            .filter(status__in=["pending", "errored"])
+            .distinct()
+        )
+        validated_data["_jobs"] = queryset
         return validated_data
 
     def create(self, validated_data):
-        ids = validated_data['_jobs'].values_list('id', flat=True)
-        validated_data['_jobs'].update(status='pending')
+        ids = validated_data["_jobs"].values_list("id", flat=True)
+        validated_data["_jobs"].update(status="pending")
         for id in ids:
             tasks.import_job_run.delay(import_job_id=id)
-        return {'jobs': list(ids)}
+        return {"jobs": list(ids)}
diff --git a/api/funkwhale_api/music/tasks.py b/api/funkwhale_api/music/tasks.py
index 7b1b489811..9dcaceec48 100644
--- a/api/funkwhale_api/music/tasks.py
+++ b/api/funkwhale_api/music/tasks.py
@@ -22,24 +22,25 @@ from . import utils as music_utils
 logger = logging.getLogger(__name__)
 
 
-@celery.app.task(name='acoustid.set_on_track_file')
-@celery.require_instance(models.TrackFile, 'track_file')
+@celery.app.task(name="acoustid.set_on_track_file")
+@celery.require_instance(models.TrackFile, "track_file")
 def set_acoustid_on_track_file(track_file):
     client = get_acoustid_client()
     result = client.get_best_match(track_file.audio_file.path)
 
     def update(id):
         track_file.acoustid_track_id = id
-        track_file.save(update_fields=['acoustid_track_id'])
+        track_file.save(update_fields=["acoustid_track_id"])
         return id
+
     if result:
-        return update(result['id'])
+        return update(result["id"])
 
 
 def import_track_from_remote(library_track):
     metadata = library_track.metadata
     try:
-        track_mbid = metadata['recording']['musicbrainz_id']
+        track_mbid = metadata["recording"]["musicbrainz_id"]
         assert track_mbid  # for null/empty values
     except (KeyError, AssertionError):
         pass
@@ -47,39 +48,43 @@ def import_track_from_remote(library_track):
         return models.Track.get_or_create_from_api(mbid=track_mbid)[0]
 
     try:
-        album_mbid = metadata['release']['musicbrainz_id']
+        album_mbid = metadata["release"]["musicbrainz_id"]
         assert album_mbid  # for null/empty values
     except (KeyError, AssertionError):
         pass
     else:
         album, _ = models.Album.get_or_create_from_api(mbid=album_mbid)
         return models.Track.get_or_create_from_title(
-            library_track.title, artist=album.artist, album=album)[0]
+            library_track.title, artist=album.artist, album=album
+        )[0]
 
     try:
-        artist_mbid = metadata['artist']['musicbrainz_id']
+        artist_mbid = metadata["artist"]["musicbrainz_id"]
         assert artist_mbid  # for null/empty values
     except (KeyError, AssertionError):
         pass
     else:
         artist, _ = models.Artist.get_or_create_from_api(mbid=artist_mbid)
         album, _ = models.Album.get_or_create_from_title(
-            library_track.album_title, artist=artist)
+            library_track.album_title, artist=artist
+        )
         return models.Track.get_or_create_from_title(
-            library_track.title, artist=artist, album=album)[0]
+            library_track.title, artist=artist, album=album
+        )[0]
 
     # worst case scenario, we have absolutely no way to link to a
     # musicbrainz resource, we rely on the name/titles
-    artist, _ = models.Artist.get_or_create_from_name(
-        library_track.artist_name)
+    artist, _ = models.Artist.get_or_create_from_name(library_track.artist_name)
     album, _ = models.Album.get_or_create_from_title(
-        library_track.album_title, artist=artist)
+        library_track.album_title, artist=artist
+    )
     return models.Track.get_or_create_from_title(
-        library_track.title, artist=artist, album=album)[0]
+        library_track.title, artist=artist, album=album
+    )[0]
 
 
 def _do_import(import_job, replace=False, use_acoustid=False):
-    logger.info('[Import Job %s] starting job', import_job.pk)
+    logger.info("[Import Job %s] starting job", import_job.pk)
     from_file = bool(import_job.audio_file)
     mbid = import_job.mbid
     acoustid_track_id = None
@@ -93,58 +98,60 @@ def _do_import(import_job, replace=False, use_acoustid=False):
         client = get_acoustid_client()
         match = client.get_best_match(import_job.audio_file.path)
         if match:
-            duration = match['recordings'][0]['duration']
-            mbid = match['recordings'][0]['id']
-            acoustid_track_id = match['id']
+            duration = match["recordings"][0]["duration"]
+            mbid = match["recordings"][0]["id"]
+            acoustid_track_id = match["id"]
     if mbid:
         logger.info(
-            '[Import Job %s] importing track from musicbrainz recording %s',
+            "[Import Job %s] importing track from musicbrainz recording %s",
             import_job.pk,
-            str(mbid))
+            str(mbid),
+        )
         track, _ = models.Track.get_or_create_from_api(mbid=mbid)
     elif import_job.audio_file:
         logger.info(
-            '[Import Job %s] importing track from uploaded track data at %s',
+            "[Import Job %s] importing track from uploaded track data at %s",
             import_job.pk,
-            import_job.audio_file.path)
-        track = audiofile_tasks.import_track_data_from_path(
-            import_job.audio_file.path)
+            import_job.audio_file.path,
+        )
+        track = audiofile_tasks.import_track_data_from_path(import_job.audio_file.path)
     elif import_job.library_track:
         logger.info(
-            '[Import Job %s] importing track from federated library track %s',
+            "[Import Job %s] importing track from federated library track %s",
             import_job.pk,
-            import_job.library_track.pk)
+            import_job.library_track.pk,
+        )
         track = import_track_from_remote(import_job.library_track)
-    elif import_job.source.startswith('file://'):
-        tf_path = import_job.source.replace('file://', '', 1)
+    elif import_job.source.startswith("file://"):
+        tf_path = import_job.source.replace("file://", "", 1)
         logger.info(
-            '[Import Job %s] importing track from local track data at %s',
+            "[Import Job %s] importing track from local track data at %s",
             import_job.pk,
-            tf_path)
-        track = audiofile_tasks.import_track_data_from_path(
-            tf_path)
+            tf_path,
+        )
+        track = audiofile_tasks.import_track_data_from_path(tf_path)
     else:
         raise ValueError(
-            'Not enough data to process import, '
-            'add a mbid, an audio file or a library track')
+            "Not enough data to process import, "
+            "add a mbid, an audio file or a library track"
+        )
 
     track_file = None
     if replace:
-        logger.info(
-            '[Import Job %s] replacing existing audio file', import_job.pk)
+        logger.info("[Import Job %s] replacing existing audio file", import_job.pk)
         track_file = track.files.first()
     elif track.files.count() > 0:
         logger.info(
-            '[Import Job %s] skipping, we already have a file for this track',
-            import_job.pk)
+            "[Import Job %s] skipping, we already have a file for this track",
+            import_job.pk,
+        )
         if import_job.audio_file:
             import_job.audio_file.delete()
-        import_job.status = 'skipped'
+        import_job.status = "skipped"
         import_job.save()
         return
 
-    track_file = track_file or models.TrackFile(
-        track=track, source=import_job.source)
+    track_file = track_file or models.TrackFile(track=track, source=import_job.source)
     track_file.acoustid_track_id = acoustid_track_id
     if from_file:
         track_file.audio_file = ContentFile(import_job.audio_file.read())
@@ -158,13 +165,11 @@ def _do_import(import_job, replace=False, use_acoustid=False):
         else:
             # no downloading, we hotlink
             pass
-    elif not import_job.audio_file and not import_job.source.startswith('file://'):
+    elif not import_job.audio_file and not import_job.source.startswith("file://"):
         # not an implace import, and we have a source, so let's download it
-        logger.info(
-            '[Import Job %s] downloading audio file from remote',
-            import_job.pk)
+        logger.info("[Import Job %s] downloading audio file from remote", import_job.pk)
         track_file.download_file()
-    elif not import_job.audio_file and import_job.source.startswith('file://'):
+    elif not import_job.audio_file and import_job.source.startswith("file://"):
         # in place import, we set mimetype from extension
         path, ext = os.path.splitext(import_job.source)
         track_file.mimetype = music_utils.get_type_from_ext(ext)
@@ -172,19 +177,15 @@ def _do_import(import_job, replace=False, use_acoustid=False):
     track_file.save()
     # if no cover is set on track album, we try to update it as well:
     if not track.album.cover:
-        logger.info(
-            '[Import Job %s] retrieving album cover',
-            import_job.pk)
+        logger.info("[Import Job %s] retrieving album cover", import_job.pk)
         update_album_cover(track.album, track_file)
-    import_job.status = 'finished'
+    import_job.status = "finished"
     import_job.track_file = track_file
     if import_job.audio_file:
         # it's imported on the track, we don't need it anymore
         import_job.audio_file.delete()
     import_job.save()
-    logger.info(
-        '[Import Job %s] job finished',
-        import_job.pk)
+    logger.info("[Import Job %s] job finished", import_job.pk)
     return track_file
 
 
@@ -199,20 +200,15 @@ def update_album_cover(album, track_file, replace=False):
         except FileNotFoundError:
             metadata = None
         if metadata:
-            cover = metadata.get_picture('cover_front')
+            cover = metadata.get_picture("cover_front")
             if cover:
                 # best case scenario, cover is embedded in the track
-                logger.info(
-                    '[Album %s] Using cover embedded in file',
-                    album.pk)
+                logger.info("[Album %s] Using cover embedded in file", album.pk)
                 return album.get_image(data=cover)
-        if track_file.source and track_file.source.startswith('file://'):
+        if track_file.source and track_file.source.startswith("file://"):
             # let's look for a cover in the same directory
-            path = os.path.dirname(track_file.source.replace('file://', '', 1))
-            logger.info(
-                '[Album %s] scanning covers from %s',
-                album.pk,
-                path)
+            path = os.path.dirname(track_file.source.replace("file://", "", 1))
+            logger.info("[Album %s] scanning covers from %s", album.pk, path)
             cover = get_cover_from_fs(path)
             if cover:
                 return album.get_image(data=cover)
@@ -220,50 +216,41 @@ def update_album_cover(album, track_file, replace=False):
         return
     try:
         logger.info(
-            '[Album %s] Fetching cover from musicbrainz release %s',
+            "[Album %s] Fetching cover from musicbrainz release %s",
             album.pk,
-            str(album.mbid))
+            str(album.mbid),
+        )
         return album.get_image()
     except ResponseError as exc:
         logger.warning(
-            '[Album %s] cannot fetch cover from musicbrainz: %s',
-            album.pk,
-            str(exc))
+            "[Album %s] cannot fetch cover from musicbrainz: %s", album.pk, str(exc)
+        )
+
 
+IMAGE_TYPES = [("jpg", "image/jpeg"), ("png", "image/png")]
 
-IMAGE_TYPES = [
-    ('jpg', 'image/jpeg'),
-    ('png', 'image/png'),
-]
 
 def get_cover_from_fs(dir_path):
     if os.path.exists(dir_path):
         for e, m in IMAGE_TYPES:
-            cover_path = os.path.join(dir_path, 'cover.{}'.format(e))
+            cover_path = os.path.join(dir_path, "cover.{}".format(e))
             if not os.path.exists(cover_path):
-                logger.debug('Cover %s does not exists', cover_path)
+                logger.debug("Cover %s does not exists", cover_path)
                 continue
-            with open(cover_path, 'rb') as c:
-                logger.info('Found cover at %s', cover_path)
-                return {
-                    'mimetype': m,
-                    'content': c.read(),
-                }
+            with open(cover_path, "rb") as c:
+                logger.info("Found cover at %s", cover_path)
+                return {"mimetype": m, "content": c.read()}
 
 
-
-@celery.app.task(name='ImportJob.run', bind=True)
+@celery.app.task(name="ImportJob.run", bind=True)
 @celery.require_instance(
-    models.ImportJob.objects.filter(
-        status__in=['pending', 'errored']),
-    'import_job')
+    models.ImportJob.objects.filter(status__in=["pending", "errored"]), "import_job"
+)
 def import_job_run(self, import_job, replace=False, use_acoustid=False):
     def mark_errored(exc):
-        logger.error(
-            '[Import Job %s] Error during import: %s',
-            import_job.pk, str(exc))
-        import_job.status = 'errored'
-        import_job.save(update_fields=['status'])
+        logger.error("[Import Job %s] Error during import: %s", import_job.pk, str(exc))
+        import_job.status = "errored"
+        import_job.save(update_fields=["status"])
 
     try:
         tf = _do_import(import_job, replace, use_acoustid=use_acoustid)
@@ -279,58 +266,56 @@ def import_job_run(self, import_job, replace=False, use_acoustid=False):
         raise
 
 
-@celery.app.task(name='ImportBatch.run')
-@celery.require_instance(models.ImportBatch, 'import_batch')
+@celery.app.task(name="ImportBatch.run")
+@celery.require_instance(models.ImportBatch, "import_batch")
 def import_batch_run(import_batch):
-    for job_id in import_batch.jobs.order_by('id').values_list('id', flat=True):
+    for job_id in import_batch.jobs.order_by("id").values_list("id", flat=True):
         import_job_run.delay(import_job_id=job_id)
 
 
-@celery.app.task(name='Lyrics.fetch_content')
-@celery.require_instance(models.Lyrics, 'lyrics')
+@celery.app.task(name="Lyrics.fetch_content")
+@celery.require_instance(models.Lyrics, "lyrics")
 def fetch_content(lyrics):
     html = lyrics_utils._get_html(lyrics.url)
     content = lyrics_utils.extract_content(html)
     cleaned_content = lyrics_utils.clean_content(content)
     lyrics.content = cleaned_content
-    lyrics.save(update_fields=['content'])
+    lyrics.save(update_fields=["content"])
 
 
-@celery.app.task(name='music.import_batch_notify_followers')
+@celery.app.task(name="music.import_batch_notify_followers")
 @celery.require_instance(
-    models.ImportBatch.objects.filter(status='finished'), 'import_batch')
+    models.ImportBatch.objects.filter(status="finished"), "import_batch"
+)
 def import_batch_notify_followers(import_batch):
-    if not preferences.get('federation__enabled'):
+    if not preferences.get("federation__enabled"):
         return
 
-    if import_batch.source == 'federation':
+    if import_batch.source == "federation":
         return
 
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
     followers = library_actor.get_approved_followers()
     jobs = import_batch.jobs.filter(
-        status='finished',
-        library_track__isnull=True,
-        track_file__isnull=False,
-    ).select_related(
-        'track_file__track__artist',
-        'track_file__track__album__artist',
-    )
+        status="finished", library_track__isnull=True, track_file__isnull=False
+    ).select_related("track_file__track__artist", "track_file__track__album__artist")
     track_files = [job.track_file for job in jobs]
-    collection = federation_serializers.CollectionSerializer({
-        'actor': library_actor,
-        'id': import_batch.get_federation_url(),
-        'items': track_files,
-        'item_serializer': federation_serializers.AudioSerializer
-    }).data
+    collection = federation_serializers.CollectionSerializer(
+        {
+            "actor": library_actor,
+            "id": import_batch.get_federation_url(),
+            "items": track_files,
+            "item_serializer": federation_serializers.AudioSerializer,
+        }
+    ).data
     for f in followers:
         create = federation_serializers.ActivitySerializer(
             {
-                'type': 'Create',
-                'id': collection['id'],
-                'object': collection,
-                'actor': library_actor.url,
-                'to': [f.url],
+                "type": "Create",
+                "id": collection["id"],
+                "object": collection,
+                "actor": library_actor.url,
+                "to": [f.url],
             }
         ).data
 
diff --git a/api/funkwhale_api/music/utils.py b/api/funkwhale_api/music/utils.py
index 3b9fbb2147..9b300ac2a4 100644
--- a/api/funkwhale_api/music/utils.py
+++ b/api/funkwhale_api/music/utils.py
@@ -6,29 +6,31 @@ import re
 from django.db.models import Q
 
 
-def normalize_query(query_string,
-                    findterms=re.compile(r'"([^"]+)"|(\S+)').findall,
-                    normspace=re.compile(r'\s{2,}').sub):
-    ''' Splits the query string in invidual keywords, getting rid of unecessary spaces
+def normalize_query(
+    query_string,
+    findterms=re.compile(r'"([^"]+)"|(\S+)').findall,
+    normspace=re.compile(r"\s{2,}").sub,
+):
+    """ Splits the query string in invidual keywords, getting rid of unecessary spaces
         and grouping quoted words together.
         Example:
 
         >>> normalize_query('  some random  words "with   quotes  " and   spaces')
         ['some', 'random', 'words', 'with quotes', 'and', 'spaces']
 
-    '''
-    return [normspace(' ', (t[0] or t[1]).strip()) for t in findterms(query_string)]
+    """
+    return [normspace(" ", (t[0] or t[1]).strip()) for t in findterms(query_string)]
 
 
 def get_query(query_string, search_fields):
-    ''' Returns a query, that is a combination of Q objects. That combination
+    """ Returns a query, that is a combination of Q objects. That combination
         aims to search keywords within a model by testing the given search fields.
 
-    '''
-    query = None # Query to search for every search term
+    """
+    query = None  # Query to search for every search term
     terms = normalize_query(query_string)
     for term in terms:
-        or_query = None # Query to search for a given term in each field
+        or_query = None  # Query to search for a given term in each field
         for field_name in search_fields:
             q = Q(**{"%s__icontains" % field_name: term})
             if or_query is None:
@@ -45,7 +47,7 @@ def get_query(query_string, search_fields):
 def guess_mimetype(f):
     b = min(1000000, f.size)
     t = magic.from_buffer(f.read(b), mime=True)
-    if not t.startswith('audio/'):
+    if not t.startswith("audio/"):
         # failure, we try guessing by extension
         mt, _ = mimetypes.guess_type(f.path)
         if mt:
@@ -54,20 +56,20 @@ def guess_mimetype(f):
 
 
 def compute_status(jobs):
-    statuses = jobs.order_by().values_list('status', flat=True).distinct()
-    errored = any([status == 'errored' for status in statuses])
+    statuses = jobs.order_by().values_list("status", flat=True).distinct()
+    errored = any([status == "errored" for status in statuses])
     if errored:
-        return 'errored'
-    pending = any([status == 'pending' for status in statuses])
+        return "errored"
+    pending = any([status == "pending" for status in statuses])
     if pending:
-        return 'pending'
-    return 'finished'
+        return "pending"
+    return "finished"
 
 
 AUDIO_EXTENSIONS_AND_MIMETYPE = [
-    ('ogg', 'audio/ogg'),
-    ('mp3', 'audio/mpeg'),
-    ('flac', 'audio/x-flac'),
+    ("ogg", "audio/ogg"),
+    ("mp3", "audio/mpeg"),
+    ("flac", "audio/x-flac"),
 ]
 
 EXTENSION_TO_MIMETYPE = {ext: mt for ext, mt in AUDIO_EXTENSIONS_AND_MIMETYPE}
@@ -79,7 +81,7 @@ def get_ext_from_type(mimetype):
 
 
 def get_type_from_ext(extension):
-    if extension.startswith('.'):
+    if extension.startswith("."):
         # we remove leading dot
         extension = extension[1:]
     return EXTENSION_TO_MIMETYPE.get(extension)
@@ -90,7 +92,7 @@ def get_audio_file_data(f):
     if not data:
         return
     d = {}
-    d['bitrate'] = data.info.bitrate
-    d['length'] = data.info.length
+    d["bitrate"] = data.info.bitrate
+    d["length"] = data.info.length
 
     return d
diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index 2850c07705..15421fa381 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -46,10 +46,9 @@ logger = logging.getLogger(__name__)
 
 
 class TagViewSetMixin(object):
-
     def get_queryset(self):
         queryset = super().get_queryset()
-        tag = self.request.query_params.get('tag')
+        tag = self.request.query_params.get("tag")
         if tag:
             queryset = queryset.filter(tags__pk=tag)
         return queryset
@@ -60,38 +59,37 @@ class ArtistViewSet(viewsets.ReadOnlyModelViewSet):
     serializer_class = serializers.ArtistWithAlbumsSerializer
     permission_classes = [ConditionalAuthentication]
     filter_class = filters.ArtistFilter
-    ordering_fields = ('id', 'name', 'creation_date')
+    ordering_fields = ("id", "name", "creation_date")
 
 
 class AlbumViewSet(viewsets.ReadOnlyModelViewSet):
     queryset = (
         models.Album.objects.all()
-                            .order_by('artist', 'release_date')
-                            .select_related()
-                            .prefetch_related(
-                                'tracks__artist',
-                                'tracks__files'))
+        .order_by("artist", "release_date")
+        .select_related()
+        .prefetch_related("tracks__artist", "tracks__files")
+    )
     serializer_class = serializers.AlbumSerializer
     permission_classes = [ConditionalAuthentication]
-    ordering_fields = ('creation_date', 'release_date', 'title')
+    ordering_fields = ("creation_date", "release_date", "title")
     filter_class = filters.AlbumFilter
 
 
 class ImportBatchViewSet(
-        mixins.CreateModelMixin,
-        mixins.ListModelMixin,
-        mixins.RetrieveModelMixin,
-        viewsets.GenericViewSet):
+    mixins.CreateModelMixin,
+    mixins.ListModelMixin,
+    mixins.RetrieveModelMixin,
+    viewsets.GenericViewSet,
+):
     queryset = (
-        models.ImportBatch.objects
-              .select_related()
-              .order_by('-creation_date')
-              .annotate(job_count=Count('jobs'))
+        models.ImportBatch.objects.select_related()
+        .order_by("-creation_date")
+        .annotate(job_count=Count("jobs"))
     )
     serializer_class = serializers.ImportBatchSerializer
     permission_classes = (HasUserPermission,)
-    required_permissions = ['library', 'upload']
-    permission_operator = 'or'
+    required_permissions = ["library", "upload"]
+    permission_operator = "or"
     filter_class = filters.ImportBatchFilter
 
     def perform_create(self, serializer):
@@ -101,51 +99,50 @@ class ImportBatchViewSet(
         qs = super().get_queryset()
         # if user do not have library permission, we limit to their
         # own jobs
-        if not self.request.user.has_permissions('library'):
+        if not self.request.user.has_permissions("library"):
             qs = qs.filter(submitted_by=self.request.user)
         return qs
 
 
 class ImportJobViewSet(
-        mixins.CreateModelMixin,
-        mixins.ListModelMixin,
-        viewsets.GenericViewSet):
-    queryset = (models.ImportJob.objects.all().select_related())
+    mixins.CreateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet
+):
+    queryset = models.ImportJob.objects.all().select_related()
     serializer_class = serializers.ImportJobSerializer
     permission_classes = (HasUserPermission,)
-    required_permissions = ['library', 'upload']
-    permission_operator = 'or'
+    required_permissions = ["library", "upload"]
+    permission_operator = "or"
     filter_class = filters.ImportJobFilter
 
     def get_queryset(self):
         qs = super().get_queryset()
         # if user do not have library permission, we limit to their
         # own jobs
-        if not self.request.user.has_permissions('library'):
+        if not self.request.user.has_permissions("library"):
             qs = qs.filter(batch__submitted_by=self.request.user)
         return qs
 
-    @list_route(methods=['get'])
+    @list_route(methods=["get"])
     def stats(self, request, *args, **kwargs):
-        if not request.user.has_permissions('library'):
+        if not request.user.has_permissions("library"):
             return Response(status=403)
         qs = models.ImportJob.objects.all()
         filterset = filters.ImportJobFilter(request.GET, queryset=qs)
         qs = filterset.qs
-        qs = qs.values('status').order_by('status')
-        qs = qs.annotate(status_count=Count('status'))
+        qs = qs.values("status").order_by("status")
+        qs = qs.annotate(status_count=Count("status"))
 
         data = {}
         for row in qs:
-            data[row['status']] = row['status_count']
+            data[row["status"]] = row["status_count"]
 
         for s, _ in models.IMPORT_STATUS_CHOICES:
             data.setdefault(s, 0)
 
-        data['count'] = sum([v for v in data.values()])
+        data["count"] = sum([v for v in data.values()])
         return Response(data)
 
-    @list_route(methods=['post'])
+    @list_route(methods=["post"])
     def run(self, request, *args, **kwargs):
         serializer = serializers.ImportJobRunSerializer(data=request.data)
         serializer.is_valid(raise_exception=True)
@@ -154,11 +151,10 @@ class ImportJobViewSet(
         return Response(payload)
 
     def perform_create(self, serializer):
-        source = 'file://' + serializer.validated_data['audio_file'].name
+        source = "file://" + serializer.validated_data["audio_file"].name
         serializer.save(source=source)
         funkwhale_utils.on_commit(
-            tasks.import_job_run.delay,
-            import_job_id=serializer.instance.pk
+            tasks.import_job_run.delay, import_job_id=serializer.instance.pk
         )
 
 
@@ -166,33 +162,34 @@ class TrackViewSet(TagViewSetMixin, viewsets.ReadOnlyModelViewSet):
     """
     A simple ViewSet for viewing and editing accounts.
     """
-    queryset = (models.Track.objects.all().for_nested_serialization())
+
+    queryset = models.Track.objects.all().for_nested_serialization()
     serializer_class = serializers.TrackSerializer
     permission_classes = [ConditionalAuthentication]
     filter_class = filters.TrackFilter
     ordering_fields = (
-        'creation_date',
-        'title',
-        'album__title',
-        'album__release_date',
-        'position',
-        'artist__name',
+        "creation_date",
+        "title",
+        "album__title",
+        "album__release_date",
+        "position",
+        "artist__name",
     )
 
     def get_queryset(self):
         queryset = super().get_queryset()
-        filter_favorites = self.request.GET.get('favorites', None)
+        filter_favorites = self.request.GET.get("favorites", None)
         user = self.request.user
-        if user.is_authenticated and filter_favorites == 'true':
+        if user.is_authenticated and filter_favorites == "true":
             queryset = queryset.filter(track_favorites__user=user)
 
         return queryset
 
-    @detail_route(methods=['get'])
+    @detail_route(methods=["get"])
     @transaction.non_atomic_requests
     def lyrics(self, request, *args, **kwargs):
         try:
-            track = models.Track.objects.get(pk=kwargs['pk'])
+            track = models.Track.objects.get(pk=kwargs["pk"])
         except models.Track.DoesNotExist:
             return Response(status=404)
 
@@ -201,7 +198,7 @@ class TrackViewSet(TagViewSetMixin, viewsets.ReadOnlyModelViewSet):
             work = track.get_work()
 
         if not work:
-            return Response({'error': 'unavailable work '}, status=404)
+            return Response({"error": "unavailable work "}, status=404)
 
         lyrics = work.fetch_lyrics()
         try:
@@ -209,7 +206,7 @@ class TrackViewSet(TagViewSetMixin, viewsets.ReadOnlyModelViewSet):
                 tasks.fetch_content(lyrics_id=lyrics.pk)
                 lyrics.refresh_from_db()
         except AttributeError:
-            return Response({'error': 'unavailable lyrics'}, status=404)
+            return Response({"error": "unavailable lyrics"}, status=404)
         serializer = serializers.LyricsSerializer(lyrics)
         return Response(serializer.data)
 
@@ -218,7 +215,7 @@ def get_file_path(audio_file):
     serve_path = settings.MUSIC_DIRECTORY_SERVE_PATH
     prefix = settings.MUSIC_DIRECTORY_PATH
     t = settings.REVERSE_PROXY_TYPE
-    if t == 'nginx':
+    if t == "nginx":
         # we have to use the internal locations
         try:
             path = audio_file.url
@@ -226,30 +223,30 @@ def get_file_path(audio_file):
             # a path was given
             if not serve_path or not prefix:
                 raise ValueError(
-                    'You need to specify MUSIC_DIRECTORY_SERVE_PATH and '
-                    'MUSIC_DIRECTORY_PATH to serve in-place imported files'
+                    "You need to specify MUSIC_DIRECTORY_SERVE_PATH and "
+                    "MUSIC_DIRECTORY_PATH to serve in-place imported files"
                 )
-            path = '/music' + audio_file.replace(prefix, '', 1)
-        return (settings.PROTECT_FILES_PATH + path).encode('utf-8')
-    if t == 'apache2':
+            path = "/music" + audio_file.replace(prefix, "", 1)
+        return (settings.PROTECT_FILES_PATH + path).encode("utf-8")
+    if t == "apache2":
         try:
             path = audio_file.path
         except AttributeError:
             # a path was given
             if not serve_path or not prefix:
                 raise ValueError(
-                    'You need to specify MUSIC_DIRECTORY_SERVE_PATH and '
-                    'MUSIC_DIRECTORY_PATH to serve in-place imported files'
+                    "You need to specify MUSIC_DIRECTORY_SERVE_PATH and "
+                    "MUSIC_DIRECTORY_PATH to serve in-place imported files"
                 )
             path = audio_file.replace(prefix, serve_path, 1)
-        return path.encode('utf-8')
+        return path.encode("utf-8")
 
 
 def handle_serve(track_file):
     f = track_file
     # we update the accessed_date
     f.accessed_date = timezone.now()
-    f.save(update_fields=['accessed_date'])
+    f.save(update_fields=["accessed_date"])
 
     mt = f.mimetype
     audio_file = f.audio_file
@@ -270,28 +267,24 @@ def handle_serve(track_file):
                 library_track.download_audio()
             track_file.library_track = library_track
             track_file.set_audio_data()
-            track_file.save(update_fields=['bitrate', 'duration', 'size'])
+            track_file.save(update_fields=["bitrate", "duration", "size"])
 
         audio_file = library_track.audio_file
         file_path = get_file_path(audio_file)
         mt = library_track.audio_mimetype
     elif audio_file:
         file_path = get_file_path(audio_file)
-    elif f.source and f.source.startswith('file://'):
-        file_path = get_file_path(f.source.replace('file://', '', 1))
+    elif f.source and f.source.startswith("file://"):
+        file_path = get_file_path(f.source.replace("file://", "", 1))
     if mt:
         response = Response(content_type=mt)
     else:
         response = Response()
     filename = f.filename
-    mapping = {
-        'nginx': 'X-Accel-Redirect',
-        'apache2': 'X-Sendfile',
-    }
+    mapping = {"nginx": "X-Accel-Redirect", "apache2": "X-Sendfile"}
     file_header = mapping[settings.REVERSE_PROXY_TYPE]
     response[file_header] = file_path
-    filename = "filename*=UTF-8''{}".format(
-        urllib.parse.quote(filename))
+    filename = "filename*=UTF-8''{}".format(urllib.parse.quote(filename))
     response["Content-Disposition"] = "attachment; {}".format(filename)
     if mt:
         response["Content-Type"] = mt
@@ -302,30 +295,29 @@ def handle_serve(track_file):
 class TrackFileViewSet(viewsets.ReadOnlyModelViewSet):
     queryset = (
         models.TrackFile.objects.all()
-            .select_related('track__artist', 'track__album')
-            .order_by('-id')
+        .select_related("track__artist", "track__album")
+        .order_by("-id")
     )
     serializer_class = serializers.TrackFileSerializer
-    authentication_classes = rest_settings.api_settings.DEFAULT_AUTHENTICATION_CLASSES + [
-        SignatureAuthentication
-    ]
+    authentication_classes = (
+        rest_settings.api_settings.DEFAULT_AUTHENTICATION_CLASSES
+        + [SignatureAuthentication]
+    )
     permission_classes = [music_permissions.Listen]
 
-    @detail_route(methods=['get'])
+    @detail_route(methods=["get"])
     def serve(self, request, *args, **kwargs):
         queryset = models.TrackFile.objects.select_related(
-            'library_track',
-            'track__album__artist',
-            'track__artist',
+            "library_track", "track__album__artist", "track__artist"
         )
         try:
-            return handle_serve(queryset.get(pk=kwargs['pk']))
+            return handle_serve(queryset.get(pk=kwargs["pk"]))
         except models.TrackFile.DoesNotExist:
             return Response(status=404)
 
 
 class TagViewSet(viewsets.ReadOnlyModelViewSet):
-    queryset = Tag.objects.all().order_by('name')
+    queryset = Tag.objects.all().order_by("name")
     serializer_class = serializers.TagSerializer
     permission_classes = [ConditionalAuthentication]
 
@@ -335,85 +327,91 @@ class Search(views.APIView):
     permission_classes = [ConditionalAuthentication]
 
     def get(self, request, *args, **kwargs):
-        query = request.GET['query']
+        query = request.GET["query"]
         results = {
             # 'tags': serializers.TagSerializer(self.get_tags(query), many=True).data,
-            'artists': serializers.ArtistWithAlbumsSerializer(self.get_artists(query), many=True).data,
-            'tracks': serializers.TrackSerializer(self.get_tracks(query), many=True).data,
-            'albums': serializers.AlbumSerializer(self.get_albums(query), many=True).data,
+            "artists": serializers.ArtistWithAlbumsSerializer(
+                self.get_artists(query), many=True
+            ).data,
+            "tracks": serializers.TrackSerializer(
+                self.get_tracks(query), many=True
+            ).data,
+            "albums": serializers.AlbumSerializer(
+                self.get_albums(query), many=True
+            ).data,
         }
         return Response(results, status=200)
 
     def get_tracks(self, query):
         search_fields = [
-            'mbid',
-            'title__unaccent',
-            'album__title__unaccent',
-            'artist__name__unaccent']
+            "mbid",
+            "title__unaccent",
+            "album__title__unaccent",
+            "artist__name__unaccent",
+        ]
         query_obj = utils.get_query(query, search_fields)
         return (
             models.Track.objects.all()
-                        .filter(query_obj)
-                        .select_related('artist', 'album__artist')
-                        .prefetch_related('files')
-        )[:self.max_results]
+            .filter(query_obj)
+            .select_related("artist", "album__artist")
+            .prefetch_related("files")
+        )[: self.max_results]
 
     def get_albums(self, query):
-        search_fields = [
-            'mbid',
-            'title__unaccent',
-            'artist__name__unaccent']
+        search_fields = ["mbid", "title__unaccent", "artist__name__unaccent"]
         query_obj = utils.get_query(query, search_fields)
         return (
             models.Album.objects.all()
-                        .filter(query_obj)
-                        .select_related()
-                        .prefetch_related(
-                            'tracks__files',
-                        )
-        )[:self.max_results]
+            .filter(query_obj)
+            .select_related()
+            .prefetch_related("tracks__files")
+        )[: self.max_results]
 
     def get_artists(self, query):
-        search_fields = ['mbid', 'name__unaccent']
+        search_fields = ["mbid", "name__unaccent"]
         query_obj = utils.get_query(query, search_fields)
-        return (
-            models.Artist.objects.all()
-                         .filter(query_obj)
-                         .with_albums()
-        )[:self.max_results]
+        return (models.Artist.objects.all().filter(query_obj).with_albums())[
+            : self.max_results
+        ]
 
     def get_tags(self, query):
-        search_fields = ['slug', 'name__unaccent']
+        search_fields = ["slug", "name__unaccent"]
         query_obj = utils.get_query(query, search_fields)
 
         # We want the shortest tag first
-        qs = Tag.objects.all().annotate(slug_length=Length('slug')).order_by('slug_length')
+        qs = (
+            Tag.objects.all()
+            .annotate(slug_length=Length("slug"))
+            .order_by("slug_length")
+        )
 
-        return qs.filter(query_obj)[:self.max_results]
+        return qs.filter(query_obj)[: self.max_results]
 
 
 class SubmitViewSet(viewsets.ViewSet):
     queryset = models.ImportBatch.objects.none()
     permission_classes = (HasUserPermission,)
-    required_permissions = ['library']
+    required_permissions = ["library"]
 
-    @list_route(methods=['post'])
+    @list_route(methods=["post"])
     @transaction.non_atomic_requests
     def single(self, request, *args, **kwargs):
         try:
-            models.Track.objects.get(mbid=request.POST['mbid'])
+            models.Track.objects.get(mbid=request.POST["mbid"])
             return Response({})
         except models.Track.DoesNotExist:
             pass
         batch = models.ImportBatch.objects.create(submitted_by=request.user)
-        job = models.ImportJob.objects.create(mbid=request.POST['mbid'], batch=batch, source=request.POST['import_url'])
+        job = models.ImportJob.objects.create(
+            mbid=request.POST["mbid"], batch=batch, source=request.POST["import_url"]
+        )
         tasks.import_job_run.delay(import_job_id=job.pk)
         serializer = serializers.ImportBatchSerializer(batch)
         return Response(serializer.data, status=201)
 
     def get_import_request(self, data):
         try:
-            raw = data['importRequest']
+            raw = data["importRequest"]
         except KeyError:
             return
 
@@ -423,57 +421,66 @@ class SubmitViewSet(viewsets.ViewSet):
         except ImportRequest.DoesNotExist:
             pass
 
-    @list_route(methods=['post'])
+    @list_route(methods=["post"])
     @transaction.non_atomic_requests
     def album(self, request, *args, **kwargs):
-        data = json.loads(request.body.decode('utf-8'))
+        data = json.loads(request.body.decode("utf-8"))
         import_request = self.get_import_request(data)
         import_data, batch = self._import_album(
-            data, request, batch=None, import_request=import_request)
+            data, request, batch=None, import_request=import_request
+        )
         return Response(import_data)
 
     @transaction.atomic
     def _import_album(self, data, request, batch=None, import_request=None):
         # we import the whole album here to prevent race conditions that occurs
         # when using get_or_create_from_api in tasks
-        album_data = api.releases.get(id=data['releaseId'], includes=models.Album.api_includes)['release']
+        album_data = api.releases.get(
+            id=data["releaseId"], includes=models.Album.api_includes
+        )["release"]
         cleaned_data = models.Album.clean_musicbrainz_data(album_data)
-        album = importers.load(models.Album, cleaned_data, album_data, import_hooks=[models.import_tracks])
+        album = importers.load(
+            models.Album, cleaned_data, album_data, import_hooks=[models.import_tracks]
+        )
         try:
             album.get_image()
         except ResponseError:
             pass
         if not batch:
             batch = models.ImportBatch.objects.create(
-                submitted_by=request.user,
-                import_request=import_request)
-        for row in data['tracks']:
+                submitted_by=request.user, import_request=import_request
+            )
+        for row in data["tracks"]:
             try:
-                models.TrackFile.objects.get(track__mbid=row['mbid'])
+                models.TrackFile.objects.get(track__mbid=row["mbid"])
             except models.TrackFile.DoesNotExist:
-                job = models.ImportJob.objects.create(mbid=row['mbid'], batch=batch, source=row['source'])
+                job = models.ImportJob.objects.create(
+                    mbid=row["mbid"], batch=batch, source=row["source"]
+                )
                 funkwhale_utils.on_commit(
-                    tasks.import_job_run.delay,
-                    import_job_id=job.pk
+                    tasks.import_job_run.delay, import_job_id=job.pk
                 )
 
         serializer = serializers.ImportBatchSerializer(batch)
         return serializer.data, batch
 
-    @list_route(methods=['post'])
+    @list_route(methods=["post"])
     @transaction.non_atomic_requests
     def artist(self, request, *args, **kwargs):
-        data = json.loads(request.body.decode('utf-8'))
+        data = json.loads(request.body.decode("utf-8"))
         import_request = self.get_import_request(data)
-        artist_data = api.artists.get(id=data['artistId'])['artist']
+        artist_data = api.artists.get(id=data["artistId"])["artist"]
         cleaned_data = models.Artist.clean_musicbrainz_data(artist_data)
-        artist = importers.load(models.Artist, cleaned_data, artist_data, import_hooks=[])
+        artist = importers.load(
+            models.Artist, cleaned_data, artist_data, import_hooks=[]
+        )
 
         import_data = []
         batch = None
-        for row in data['albums']:
+        for row in data["albums"]:
             row_data, batch = self._import_album(
-                row, request, batch=batch, import_request=import_request)
+                row, request, batch=batch, import_request=import_request
+            )
             import_data.append(row_data)
 
         return Response(import_data[0])
diff --git a/api/funkwhale_api/musicbrainz/client.py b/api/funkwhale_api/musicbrainz/client.py
index 8e7076a78b..b3749a93b3 100644
--- a/api/funkwhale_api/musicbrainz/client.py
+++ b/api/funkwhale_api/musicbrainz/client.py
@@ -5,17 +5,17 @@ from django.conf import settings
 from funkwhale_api import __version__
 
 _api = musicbrainzngs
-_api.set_useragent('funkwhale', str(__version__), settings.FUNKWHALE_URL)
+_api.set_useragent("funkwhale", str(__version__), settings.FUNKWHALE_URL)
 
 
-store = memoize.djangocache.Cache('default')
-memo = memoize.Memoizer(store, namespace='memoize:musicbrainz')
+store = memoize.djangocache.Cache("default")
+memo = memoize.Memoizer(store, namespace="memoize:musicbrainz")
 
 
 def clean_artist_search(query, **kwargs):
     cleaned_kwargs = {}
-    if kwargs.get('name'):
-        cleaned_kwargs['artist'] = kwargs.get('name')
+    if kwargs.get("name"):
+        cleaned_kwargs["artist"] = kwargs.get("name")
     return _api.search_artists(query, **cleaned_kwargs)
 
 
@@ -23,55 +23,43 @@ class API(object):
     _api = _api
 
     class artists(object):
-        search = memo(
-            clean_artist_search, max_age=settings.MUSICBRAINZ_CACHE_DURATION)
-        get = memo(
-            _api.get_artist_by_id,
-            max_age=settings.MUSICBRAINZ_CACHE_DURATION)
+        search = memo(clean_artist_search, max_age=settings.MUSICBRAINZ_CACHE_DURATION)
+        get = memo(_api.get_artist_by_id, max_age=settings.MUSICBRAINZ_CACHE_DURATION)
 
     class images(object):
         get_front = memo(
-            _api.get_image_front,
-            max_age=settings.MUSICBRAINZ_CACHE_DURATION)
+            _api.get_image_front, max_age=settings.MUSICBRAINZ_CACHE_DURATION
+        )
 
     class recordings(object):
         search = memo(
-            _api.search_recordings,
-            max_age=settings.MUSICBRAINZ_CACHE_DURATION)
+            _api.search_recordings, max_age=settings.MUSICBRAINZ_CACHE_DURATION
+        )
         get = memo(
-            _api.get_recording_by_id,
-            max_age=settings.MUSICBRAINZ_CACHE_DURATION)
+            _api.get_recording_by_id, max_age=settings.MUSICBRAINZ_CACHE_DURATION
+        )
 
     class works(object):
-        search = memo(
-            _api.search_works,
-            max_age=settings.MUSICBRAINZ_CACHE_DURATION)
-        get = memo(
-            _api.get_work_by_id,
-            max_age=settings.MUSICBRAINZ_CACHE_DURATION)
+        search = memo(_api.search_works, max_age=settings.MUSICBRAINZ_CACHE_DURATION)
+        get = memo(_api.get_work_by_id, max_age=settings.MUSICBRAINZ_CACHE_DURATION)
 
     class releases(object):
-        search = memo(
-            _api.search_releases,
-            max_age=settings.MUSICBRAINZ_CACHE_DURATION)
-        get = memo(
-            _api.get_release_by_id,
-            max_age=settings.MUSICBRAINZ_CACHE_DURATION)
-        browse = memo(
-            _api.browse_releases,
-            max_age=settings.MUSICBRAINZ_CACHE_DURATION)
+        search = memo(_api.search_releases, max_age=settings.MUSICBRAINZ_CACHE_DURATION)
+        get = memo(_api.get_release_by_id, max_age=settings.MUSICBRAINZ_CACHE_DURATION)
+        browse = memo(_api.browse_releases, max_age=settings.MUSICBRAINZ_CACHE_DURATION)
         # get_image_front = _api.get_image_front
 
     class release_groups(object):
         search = memo(
-            _api.search_release_groups,
-            max_age=settings.MUSICBRAINZ_CACHE_DURATION)
+            _api.search_release_groups, max_age=settings.MUSICBRAINZ_CACHE_DURATION
+        )
         get = memo(
-            _api.get_release_group_by_id,
-            max_age=settings.MUSICBRAINZ_CACHE_DURATION)
+            _api.get_release_group_by_id, max_age=settings.MUSICBRAINZ_CACHE_DURATION
+        )
         browse = memo(
-            _api.browse_release_groups,
-            max_age=settings.MUSICBRAINZ_CACHE_DURATION)
+            _api.browse_release_groups, max_age=settings.MUSICBRAINZ_CACHE_DURATION
+        )
         # get_image_front = _api.get_image_front
 
+
 api = API()
diff --git a/api/funkwhale_api/musicbrainz/urls.py b/api/funkwhale_api/musicbrainz/urls.py
index 7befe49ab5..edb4212023 100644
--- a/api/funkwhale_api/musicbrainz/urls.py
+++ b/api/funkwhale_api/musicbrainz/urls.py
@@ -4,20 +4,28 @@ from rest_framework import routers
 from . import views
 
 router = routers.SimpleRouter()
-router.register(r'search', views.SearchViewSet, 'search')
+router.register(r"search", views.SearchViewSet, "search")
 urlpatterns = [
-    url('releases/(?P<uuid>[0-9a-z-]+)/$',
+    url(
+        "releases/(?P<uuid>[0-9a-z-]+)/$",
         views.ReleaseDetail.as_view(),
-        name='release-detail'),
-    url('artists/(?P<uuid>[0-9a-z-]+)/$',
+        name="release-detail",
+    ),
+    url(
+        "artists/(?P<uuid>[0-9a-z-]+)/$",
         views.ArtistDetail.as_view(),
-        name='artist-detail'),
-    url('release-groups/browse/(?P<artist_uuid>[0-9a-z-]+)/$',
+        name="artist-detail",
+    ),
+    url(
+        "release-groups/browse/(?P<artist_uuid>[0-9a-z-]+)/$",
         views.ReleaseGroupBrowse.as_view(),
-        name='release-group-browse'),
-    url('releases/browse/(?P<release_group_uuid>[0-9a-z-]+)/$',
+        name="release-group-browse",
+    ),
+    url(
+        "releases/browse/(?P<release_group_uuid>[0-9a-z-]+)/$",
         views.ReleaseBrowse.as_view(),
-        name='release-browse'),
+        name="release-browse",
+    ),
     # url('release-groups/(?P<uuid>[0-9a-z-]+)/$',
     #     views.ReleaseGroupDetail.as_view(),
     #     name='release-group-detail'),
diff --git a/api/funkwhale_api/musicbrainz/views.py b/api/funkwhale_api/musicbrainz/views.py
index 5c101b1619..64d4ee1523 100644
--- a/api/funkwhale_api/musicbrainz/views.py
+++ b/api/funkwhale_api/musicbrainz/views.py
@@ -14,8 +14,7 @@ class ReleaseDetail(APIView):
     permission_classes = [ConditionalAuthentication]
 
     def get(self, request, *args, **kwargs):
-        result = api.releases.get(
-            id=kwargs['uuid'], includes=['artists', 'recordings'])
+        result = api.releases.get(id=kwargs["uuid"], includes=["artists", "recordings"])
         return Response(result)
 
 
@@ -23,9 +22,7 @@ class ArtistDetail(APIView):
     permission_classes = [ConditionalAuthentication]
 
     def get(self, request, *args, **kwargs):
-        result = api.artists.get(
-            id=kwargs['uuid'],
-            includes=['release-groups'])
+        result = api.artists.get(id=kwargs["uuid"], includes=["release-groups"])
         # import json; print(json.dumps(result, indent=4))
         return Response(result)
 
@@ -34,8 +31,7 @@ class ReleaseGroupBrowse(APIView):
     permission_classes = [ConditionalAuthentication]
 
     def get(self, request, *args, **kwargs):
-        result = api.release_groups.browse(
-            artist=kwargs['artist_uuid'])
+        result = api.release_groups.browse(artist=kwargs["artist_uuid"])
         return Response(result)
 
 
@@ -44,29 +40,30 @@ class ReleaseBrowse(APIView):
 
     def get(self, request, *args, **kwargs):
         result = api.releases.browse(
-            release_group=kwargs['release_group_uuid'],
-            includes=['recordings', 'artist-credits'])
+            release_group=kwargs["release_group_uuid"],
+            includes=["recordings", "artist-credits"],
+        )
         return Response(result)
 
 
 class SearchViewSet(viewsets.ViewSet):
     permission_classes = [ConditionalAuthentication]
 
-    @list_route(methods=['get'])
+    @list_route(methods=["get"])
     def recordings(self, request, *args, **kwargs):
-        query = request.GET['query']
+        query = request.GET["query"]
         results = api.recordings.search(query)
         return Response(results)
 
-    @list_route(methods=['get'])
+    @list_route(methods=["get"])
     def releases(self, request, *args, **kwargs):
-        query = request.GET['query']
+        query = request.GET["query"]
         results = api.releases.search(query)
         return Response(results)
 
-    @list_route(methods=['get'])
+    @list_route(methods=["get"])
     def artists(self, request, *args, **kwargs):
-        query = request.GET['query']
+        query = request.GET["query"]
         results = api.artists.search(query)
         # results = musicbrainzngs.search_artists(query)
         return Response(results)
diff --git a/api/funkwhale_api/playlists/admin.py b/api/funkwhale_api/playlists/admin.py
index 68e447f384..98ced232ee 100644
--- a/api/funkwhale_api/playlists/admin.py
+++ b/api/funkwhale_api/playlists/admin.py
@@ -5,13 +5,13 @@ from . import models
 
 @admin.register(models.Playlist)
 class PlaylistAdmin(admin.ModelAdmin):
-    list_display = ['name', 'user', 'privacy_level', 'creation_date']
-    search_fields = ['name', ]
+    list_display = ["name", "user", "privacy_level", "creation_date"]
+    search_fields = ["name"]
     list_select_related = True
 
 
 @admin.register(models.PlaylistTrack)
 class PlaylistTrackAdmin(admin.ModelAdmin):
-    list_display = ['playlist', 'track', 'index']
-    search_fields = ['track__name', 'playlist__name']
+    list_display = ["playlist", "track", "index"]
+    search_fields = ["track__name", "playlist__name"]
     list_select_related = True
diff --git a/api/funkwhale_api/playlists/dynamic_preferences_registry.py b/api/funkwhale_api/playlists/dynamic_preferences_registry.py
index b717177a23..5a2043452b 100644
--- a/api/funkwhale_api/playlists/dynamic_preferences_registry.py
+++ b/api/funkwhale_api/playlists/dynamic_preferences_registry.py
@@ -3,16 +3,14 @@ from dynamic_preferences.registries import global_preferences_registry
 
 from funkwhale_api.common import preferences
 
-playlists = types.Section('playlists')
+playlists = types.Section("playlists")
 
 
 @global_preferences_registry.register
 class MaxTracks(preferences.DefaultFromSettingMixin, types.IntegerPreference):
     show_in_api = True
     section = playlists
-    name = 'max_tracks'
-    verbose_name = 'Max tracks per playlist'
-    setting = 'PLAYLISTS_MAX_TRACKS'
-    field_kwargs = {
-        'required': False,
-    }
+    name = "max_tracks"
+    verbose_name = "Max tracks per playlist"
+    setting = "PLAYLISTS_MAX_TRACKS"
+    field_kwargs = {"required": False}
diff --git a/api/funkwhale_api/playlists/factories.py b/api/funkwhale_api/playlists/factories.py
index cddea60024..ff031945a5 100644
--- a/api/funkwhale_api/playlists/factories.py
+++ b/api/funkwhale_api/playlists/factories.py
@@ -7,11 +7,11 @@ from funkwhale_api.users.factories import UserFactory
 
 @registry.register
 class PlaylistFactory(factory.django.DjangoModelFactory):
-    name = factory.Faker('name')
+    name = factory.Faker("name")
     user = factory.SubFactory(UserFactory)
 
     class Meta:
-        model = 'playlists.Playlist'
+        model = "playlists.Playlist"
 
 
 @registry.register
@@ -20,4 +20,4 @@ class PlaylistTrackFactory(factory.django.DjangoModelFactory):
     track = factory.SubFactory(TrackFactory)
 
     class Meta:
-        model = 'playlists.PlaylistTrack'
+        model = "playlists.PlaylistTrack"
diff --git a/api/funkwhale_api/playlists/filters.py b/api/funkwhale_api/playlists/filters.py
index bc49415100..ae9f0226f2 100644
--- a/api/funkwhale_api/playlists/filters.py
+++ b/api/funkwhale_api/playlists/filters.py
@@ -5,18 +5,13 @@ from funkwhale_api.music import utils
 from . import models
 
 
-
 class PlaylistFilter(filters.FilterSet):
-    q = filters.CharFilter(name='_', method='filter_q')
+    q = filters.CharFilter(name="_", method="filter_q")
 
     class Meta:
         model = models.Playlist
-        fields = {
-            'user': ['exact'],
-            'name': ['exact', 'icontains'],
-            'q': 'exact',
-        }
+        fields = {"user": ["exact"], "name": ["exact", "icontains"], "q": "exact"}
 
     def filter_q(self, queryset, name, value):
-        query = utils.get_query(value, ['name', 'user__username'])
+        query = utils.get_query(value, ["name", "user__username"])
         return queryset.filter(query)
diff --git a/api/funkwhale_api/playlists/migrations/0001_initial.py b/api/funkwhale_api/playlists/migrations/0001_initial.py
index 987b2f9cfe..68e66d7637 100644
--- a/api/funkwhale_api/playlists/migrations/0001_initial.py
+++ b/api/funkwhale_api/playlists/migrations/0001_initial.py
@@ -10,34 +10,84 @@ class Migration(migrations.Migration):
 
     dependencies = [
         migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-        ('music', '0012_auto_20161122_1905'),
+        ("music", "0012_auto_20161122_1905"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='Playlist',
+            name="Playlist",
             fields=[
-                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
-                ('name', models.CharField(max_length=50)),
-                ('is_public', models.BooleanField(default=False)),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='playlists', on_delete=models.CASCADE)),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        verbose_name="ID",
+                        primary_key=True,
+                        serialize=False,
+                    ),
+                ),
+                ("name", models.CharField(max_length=50)),
+                ("is_public", models.BooleanField(default=False)),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        to=settings.AUTH_USER_MODEL,
+                        related_name="playlists",
+                        on_delete=models.CASCADE,
+                    ),
+                ),
             ],
         ),
         migrations.CreateModel(
-            name='PlaylistTrack',
+            name="PlaylistTrack",
             fields=[
-                ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)),
-                ('lft', models.PositiveIntegerField(db_index=True, editable=False)),
-                ('rght', models.PositiveIntegerField(db_index=True, editable=False)),
-                ('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
-                ('position', models.PositiveIntegerField(db_index=True, editable=False)),
-                ('playlist', models.ForeignKey(to='playlists.Playlist', related_name='playlist_tracks', on_delete=models.CASCADE)),
-                ('previous', models.OneToOneField(null=True, to='playlists.PlaylistTrack', related_name='next', blank=True, on_delete=models.CASCADE)),
-                ('track', models.ForeignKey(to='music.Track', related_name='playlist_tracks', on_delete=models.CASCADE)),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        verbose_name="ID",
+                        primary_key=True,
+                        serialize=False,
+                    ),
+                ),
+                ("lft", models.PositiveIntegerField(db_index=True, editable=False)),
+                ("rght", models.PositiveIntegerField(db_index=True, editable=False)),
+                ("tree_id", models.PositiveIntegerField(db_index=True, editable=False)),
+                (
+                    "position",
+                    models.PositiveIntegerField(db_index=True, editable=False),
+                ),
+                (
+                    "playlist",
+                    models.ForeignKey(
+                        to="playlists.Playlist",
+                        related_name="playlist_tracks",
+                        on_delete=models.CASCADE,
+                    ),
+                ),
+                (
+                    "previous",
+                    models.OneToOneField(
+                        null=True,
+                        to="playlists.PlaylistTrack",
+                        related_name="next",
+                        blank=True,
+                        on_delete=models.CASCADE,
+                    ),
+                ),
+                (
+                    "track",
+                    models.ForeignKey(
+                        to="music.Track",
+                        related_name="playlist_tracks",
+                        on_delete=models.CASCADE,
+                    ),
+                ),
             ],
-            options={
-                'ordering': ('-playlist', 'position'),
-            },
+            options={"ordering": ("-playlist", "position")},
         ),
     ]
diff --git a/api/funkwhale_api/playlists/migrations/0002_auto_20180316_2217.py b/api/funkwhale_api/playlists/migrations/0002_auto_20180316_2217.py
index 23d0a8eab6..8245797bfc 100644
--- a/api/funkwhale_api/playlists/migrations/0002_auto_20180316_2217.py
+++ b/api/funkwhale_api/playlists/migrations/0002_auto_20180316_2217.py
@@ -5,18 +5,22 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('playlists', '0001_initial'),
-    ]
+    dependencies = [("playlists", "0001_initial")]
 
     operations = [
-        migrations.RemoveField(
-            model_name='playlist',
-            name='is_public',
-        ),
+        migrations.RemoveField(model_name="playlist", name="is_public"),
         migrations.AddField(
-            model_name='playlist',
-            name='privacy_level',
-            field=models.CharField(choices=[('me', 'Only me'), ('followers', 'Me and my followers'), ('instance', 'Everyone on my instance, and my followers'), ('everyone', 'Everyone, including people on other instances')], default='instance', max_length=30),
+            model_name="playlist",
+            name="privacy_level",
+            field=models.CharField(
+                choices=[
+                    ("me", "Only me"),
+                    ("followers", "Me and my followers"),
+                    ("instance", "Everyone on my instance, and my followers"),
+                    ("everyone", "Everyone, including people on other instances"),
+                ],
+                default="instance",
+                max_length=30,
+            ),
         ),
     ]
diff --git a/api/funkwhale_api/playlists/migrations/0003_auto_20180319_1214.py b/api/funkwhale_api/playlists/migrations/0003_auto_20180319_1214.py
index 0284f8f2cf..d4d28b9e04 100644
--- a/api/funkwhale_api/playlists/migrations/0003_auto_20180319_1214.py
+++ b/api/funkwhale_api/playlists/migrations/0003_auto_20180319_1214.py
@@ -6,47 +6,28 @@ import django.utils.timezone
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('playlists', '0002_auto_20180316_2217'),
-    ]
+    dependencies = [("playlists", "0002_auto_20180316_2217")]
 
     operations = [
         migrations.AlterModelOptions(
-            name='playlisttrack',
-            options={'ordering': ('-playlist', 'index')},
+            name="playlisttrack", options={"ordering": ("-playlist", "index")}
         ),
         migrations.AddField(
-            model_name='playlisttrack',
-            name='creation_date',
+            model_name="playlisttrack",
+            name="creation_date",
             field=models.DateTimeField(default=django.utils.timezone.now),
         ),
         migrations.AddField(
-            model_name='playlisttrack',
-            name='index',
+            model_name="playlisttrack",
+            name="index",
             field=models.PositiveIntegerField(null=True),
         ),
-        migrations.RemoveField(
-            model_name='playlisttrack',
-            name='lft',
-        ),
-        migrations.RemoveField(
-            model_name='playlisttrack',
-            name='position',
-        ),
-        migrations.RemoveField(
-            model_name='playlisttrack',
-            name='previous',
-        ),
-        migrations.RemoveField(
-            model_name='playlisttrack',
-            name='rght',
-        ),
-        migrations.RemoveField(
-            model_name='playlisttrack',
-            name='tree_id',
-        ),
+        migrations.RemoveField(model_name="playlisttrack", name="lft"),
+        migrations.RemoveField(model_name="playlisttrack", name="position"),
+        migrations.RemoveField(model_name="playlisttrack", name="previous"),
+        migrations.RemoveField(model_name="playlisttrack", name="rght"),
+        migrations.RemoveField(model_name="playlisttrack", name="tree_id"),
         migrations.AlterUniqueTogether(
-            name='playlisttrack',
-            unique_together={('playlist', 'index')},
+            name="playlisttrack", unique_together={("playlist", "index")}
         ),
     ]
diff --git a/api/funkwhale_api/playlists/migrations/0004_auto_20180320_1713.py b/api/funkwhale_api/playlists/migrations/0004_auto_20180320_1713.py
index 415b53612a..75c42a5c0c 100644
--- a/api/funkwhale_api/playlists/migrations/0004_auto_20180320_1713.py
+++ b/api/funkwhale_api/playlists/migrations/0004_auto_20180320_1713.py
@@ -5,23 +5,18 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('playlists', '0003_auto_20180319_1214'),
-    ]
+    dependencies = [("playlists", "0003_auto_20180319_1214")]
 
     operations = [
         migrations.AddField(
-            model_name='playlist',
-            name='modification_date',
+            model_name="playlist",
+            name="modification_date",
             field=models.DateTimeField(auto_now=True),
         ),
         migrations.AlterField(
-            model_name='playlisttrack',
-            name='index',
+            model_name="playlisttrack",
+            name="index",
             field=models.PositiveIntegerField(blank=True, null=True),
         ),
-        migrations.AlterUniqueTogether(
-            name='playlisttrack',
-            unique_together=set(),
-        ),
+        migrations.AlterUniqueTogether(name="playlisttrack", unique_together=set()),
     ]
diff --git a/api/funkwhale_api/playlists/models.py b/api/funkwhale_api/playlists/models.py
index f5132e12dd..ef787d276f 100644
--- a/api/funkwhale_api/playlists/models.py
+++ b/api/funkwhale_api/playlists/models.py
@@ -11,17 +11,16 @@ from funkwhale_api.common import preferences
 
 class PlaylistQuerySet(models.QuerySet):
     def with_tracks_count(self):
-        return self.annotate(
-            _tracks_count=models.Count('playlist_tracks'))
+        return self.annotate(_tracks_count=models.Count("playlist_tracks"))
 
 
 class Playlist(models.Model):
     name = models.CharField(max_length=50)
     user = models.ForeignKey(
-        'users.User', related_name="playlists", on_delete=models.CASCADE)
+        "users.User", related_name="playlists", on_delete=models.CASCADE
+    )
     creation_date = models.DateTimeField(default=timezone.now)
-    modification_date = models.DateTimeField(
-        auto_now=True)
+    modification_date = models.DateTimeField(auto_now=True)
     privacy_level = fields.get_privacy_field()
 
     objects = PlaylistQuerySet.as_manager()
@@ -51,89 +50,91 @@ class Playlist(models.Model):
             index = total
 
         if index > total:
-            raise exceptions.ValidationError('Index is not continuous')
+            raise exceptions.ValidationError("Index is not continuous")
 
         if index < 0:
-            raise exceptions.ValidationError('Index must be zero or positive')
+            raise exceptions.ValidationError("Index must be zero or positive")
 
         if move:
             # we remove the index temporarily, to avoid integrity errors
             plt.index = None
-            plt.save(update_fields=['index'])
+            plt.save(update_fields=["index"])
             if index > old_index:
                 # new index is higher than current, we decrement previous tracks
-                to_update = existing.filter(
-                    index__gt=old_index, index__lte=index)
-                to_update.update(index=models.F('index') - 1)
+                to_update = existing.filter(index__gt=old_index, index__lte=index)
+                to_update.update(index=models.F("index") - 1)
             if index < old_index:
                 # new index is lower than current, we increment next tracks
                 to_update = existing.filter(index__lt=old_index, index__gte=index)
-                to_update.update(index=models.F('index') + 1)
+                to_update.update(index=models.F("index") + 1)
         else:
             to_update = existing.filter(index__gte=index)
-            to_update.update(index=models.F('index') + 1)
+            to_update.update(index=models.F("index") + 1)
 
         plt.index = index
-        plt.save(update_fields=['index'])
-        self.save(update_fields=['modification_date'])
+        plt.save(update_fields=["index"])
+        self.save(update_fields=["modification_date"])
         return index
 
     @transaction.atomic
     def remove(self, index):
         existing = self.playlist_tracks.select_for_update()
-        self.save(update_fields=['modification_date'])
+        self.save(update_fields=["modification_date"])
         to_update = existing.filter(index__gt=index)
-        return to_update.update(index=models.F('index') - 1)
+        return to_update.update(index=models.F("index") - 1)
 
     @transaction.atomic
     def insert_many(self, tracks):
         existing = self.playlist_tracks.select_for_update()
         now = timezone.now()
         total = existing.filter(index__isnull=False).count()
-        max_tracks = preferences.get('playlists__max_tracks')
+        max_tracks = preferences.get("playlists__max_tracks")
         if existing.count() + len(tracks) > max_tracks:
             raise exceptions.ValidationError(
-                'Playlist would reach the maximum of {} tracks'.format(
-                    max_tracks))
-        self.save(update_fields=['modification_date'])
+                "Playlist would reach the maximum of {} tracks".format(max_tracks)
+            )
+        self.save(update_fields=["modification_date"])
         start = total
         plts = [
             PlaylistTrack(
-                creation_date=now, playlist=self, track=track, index=start+i)
+                creation_date=now, playlist=self, track=track, index=start + i
+            )
             for i, track in enumerate(tracks)
         ]
         return PlaylistTrack.objects.bulk_create(plts)
 
+
 class PlaylistTrackQuerySet(models.QuerySet):
     def for_nested_serialization(self):
-        return (self.select_related()
-                    .select_related('track__album__artist')
-                    .prefetch_related(
-                        'track__tags',
-                        'track__files',
-                        'track__artist__albums__tracks__tags'))
+        return (
+            self.select_related()
+            .select_related("track__album__artist")
+            .prefetch_related(
+                "track__tags", "track__files", "track__artist__albums__tracks__tags"
+            )
+        )
 
 
 class PlaylistTrack(models.Model):
     track = models.ForeignKey(
-        'music.Track',
-        related_name='playlist_tracks',
-        on_delete=models.CASCADE)
+        "music.Track", related_name="playlist_tracks", on_delete=models.CASCADE
+    )
     index = models.PositiveIntegerField(null=True, blank=True)
     playlist = models.ForeignKey(
-        Playlist, related_name='playlist_tracks', on_delete=models.CASCADE)
+        Playlist, related_name="playlist_tracks", on_delete=models.CASCADE
+    )
     creation_date = models.DateTimeField(default=timezone.now)
 
     objects = PlaylistTrackQuerySet.as_manager()
 
     class Meta:
-        ordering = ('-playlist', 'index')
-        unique_together = ('playlist', 'index')
+        ordering = ("-playlist", "index")
+        unique_together = ("playlist", "index")
 
     def delete(self, *args, **kwargs):
         playlist = self.playlist
         index = self.index
-        update_indexes = kwargs.pop('update_indexes', False)
+        update_indexes = kwargs.pop("update_indexes", False)
         r = super().delete(*args, **kwargs)
         if index is not None and update_indexes:
             playlist.remove(index)
diff --git a/api/funkwhale_api/playlists/serializers.py b/api/funkwhale_api/playlists/serializers.py
index 3f01fd6894..3fac68b0e9 100644
--- a/api/funkwhale_api/playlists/serializers.py
+++ b/api/funkwhale_api/playlists/serializers.py
@@ -15,42 +15,42 @@ class PlaylistTrackSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = models.PlaylistTrack
-        fields = ('id', 'track', 'playlist', 'index', 'creation_date')
+        fields = ("id", "track", "playlist", "index", "creation_date")
 
 
 class PlaylistTrackWriteSerializer(serializers.ModelSerializer):
-    index = serializers.IntegerField(
-        required=False, min_value=0, allow_null=True)
+    index = serializers.IntegerField(required=False, min_value=0, allow_null=True)
 
     class Meta:
         model = models.PlaylistTrack
-        fields = ('id', 'track', 'playlist', 'index')
+        fields = ("id", "track", "playlist", "index")
 
     def validate_playlist(self, value):
-        if self.context.get('request'):
+        if self.context.get("request"):
             # validate proper ownership on the playlist
-            if self.context['request'].user != value.user:
+            if self.context["request"].user != value.user:
                 raise serializers.ValidationError(
-                    'You do not have the permission to edit this playlist')
+                    "You do not have the permission to edit this playlist"
+                )
         existing = value.playlist_tracks.count()
-        max_tracks = preferences.get('playlists__max_tracks')
+        max_tracks = preferences.get("playlists__max_tracks")
         if existing >= max_tracks:
             raise serializers.ValidationError(
-                'Playlist has reached the maximum of {} tracks'.format(
-                    max_tracks))
+                "Playlist has reached the maximum of {} tracks".format(max_tracks)
+            )
         return value
 
     @transaction.atomic
     def create(self, validated_data):
-        index = validated_data.pop('index', None)
+        index = validated_data.pop("index", None)
         instance = super().create(validated_data)
         instance.playlist.insert(instance, index)
         return instance
 
     @transaction.atomic
     def update(self, instance, validated_data):
-        update_index = 'index' in validated_data
-        index = validated_data.pop('index', None)
+        update_index = "index" in validated_data
+        index = validated_data.pop("index", None)
         super().update(instance, validated_data)
         if update_index:
             instance.playlist.insert(instance, index)
@@ -71,17 +71,15 @@ class PlaylistSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Playlist
         fields = (
-            'id',
-            'name',
-            'tracks_count',
-            'user',
-            'modification_date',
-            'creation_date',
-            'privacy_level',)
-        read_only_fields = [
-            'id',
-            'modification_date',
-            'creation_date',]
+            "id",
+            "name",
+            "tracks_count",
+            "user",
+            "modification_date",
+            "creation_date",
+            "privacy_level",
+        )
+        read_only_fields = ["id", "modification_date", "creation_date"]
 
     def get_tracks_count(self, obj):
         try:
@@ -93,4 +91,5 @@ class PlaylistSerializer(serializers.ModelSerializer):
 
 class PlaylistAddManySerializer(serializers.Serializer):
     tracks = serializers.PrimaryKeyRelatedField(
-        many=True, queryset=Track.objects.for_nested_serialization())
+        many=True, queryset=Track.objects.for_nested_serialization()
+    )
diff --git a/api/funkwhale_api/playlists/views.py b/api/funkwhale_api/playlists/views.py
index 683f903888..f45d8b2720 100644
--- a/api/funkwhale_api/playlists/views.py
+++ b/api/funkwhale_api/playlists/views.py
@@ -16,108 +16,110 @@ from . import filters
 from . import models
 from . import serializers
 
+
 class PlaylistViewSet(
-        mixins.RetrieveModelMixin,
-        mixins.CreateModelMixin,
-        mixins.UpdateModelMixin,
-        mixins.DestroyModelMixin,
-        mixins.ListModelMixin,
-        viewsets.GenericViewSet):
+    mixins.RetrieveModelMixin,
+    mixins.CreateModelMixin,
+    mixins.UpdateModelMixin,
+    mixins.DestroyModelMixin,
+    mixins.ListModelMixin,
+    viewsets.GenericViewSet,
+):
 
     serializer_class = serializers.PlaylistSerializer
     queryset = (
-        models.Playlist.objects.all().select_related('user')
-              .annotate(tracks_count=Count('playlist_tracks'))
+        models.Playlist.objects.all()
+        .select_related("user")
+        .annotate(tracks_count=Count("playlist_tracks"))
     )
     permission_classes = [
         permissions.ConditionalAuthentication,
         permissions.OwnerPermission,
         IsAuthenticatedOrReadOnly,
     ]
-    owner_checks = ['write']
+    owner_checks = ["write"]
     filter_class = filters.PlaylistFilter
-    ordering_fields = ('id', 'name', 'creation_date', 'modification_date')
+    ordering_fields = ("id", "name", "creation_date", "modification_date")
 
-    @detail_route(methods=['get'])
+    @detail_route(methods=["get"])
     def tracks(self, request, *args, **kwargs):
         playlist = self.get_object()
         plts = playlist.playlist_tracks.all().for_nested_serialization()
         serializer = serializers.PlaylistTrackSerializer(plts, many=True)
-        data = {
-            'count': len(plts),
-            'results': serializer.data
-        }
+        data = {"count": len(plts), "results": serializer.data}
         return Response(data, status=200)
 
-    @detail_route(methods=['post'])
+    @detail_route(methods=["post"])
     @transaction.atomic
     def add(self, request, *args, **kwargs):
         playlist = self.get_object()
         serializer = serializers.PlaylistAddManySerializer(data=request.data)
         serializer.is_valid(raise_exception=True)
         try:
-            plts = playlist.insert_many(serializer.validated_data['tracks'])
+            plts = playlist.insert_many(serializer.validated_data["tracks"])
         except exceptions.ValidationError as e:
-            payload = {'playlist': e.detail}
+            payload = {"playlist": e.detail}
             return Response(payload, status=400)
         ids = [p.id for p in plts]
-        plts = models.PlaylistTrack.objects.filter(
-            pk__in=ids).order_by('index').for_nested_serialization()
+        plts = (
+            models.PlaylistTrack.objects.filter(pk__in=ids)
+            .order_by("index")
+            .for_nested_serialization()
+        )
         serializer = serializers.PlaylistTrackSerializer(plts, many=True)
-        data = {
-            'count': len(plts),
-            'results': serializer.data
-        }
+        data = {"count": len(plts), "results": serializer.data}
         return Response(data, status=201)
 
-    @detail_route(methods=['delete'])
+    @detail_route(methods=["delete"])
     @transaction.atomic
     def clear(self, request, *args, **kwargs):
         playlist = self.get_object()
         playlist.playlist_tracks.all().delete()
-        playlist.save(update_fields=['modification_date'])
+        playlist.save(update_fields=["modification_date"])
         return Response(status=204)
 
     def get_queryset(self):
-        return self.queryset.filter(
-            fields.privacy_level_query(self.request.user))
+        return self.queryset.filter(fields.privacy_level_query(self.request.user))
 
     def perform_create(self, serializer):
         return serializer.save(
             user=self.request.user,
             privacy_level=serializer.validated_data.get(
-                'privacy_level', self.request.user.privacy_level)
+                "privacy_level", self.request.user.privacy_level
+            ),
         )
 
 
 class PlaylistTrackViewSet(
-        mixins.RetrieveModelMixin,
-        mixins.CreateModelMixin,
-        mixins.UpdateModelMixin,
-        mixins.DestroyModelMixin,
-        mixins.ListModelMixin,
-        viewsets.GenericViewSet):
+    mixins.RetrieveModelMixin,
+    mixins.CreateModelMixin,
+    mixins.UpdateModelMixin,
+    mixins.DestroyModelMixin,
+    mixins.ListModelMixin,
+    viewsets.GenericViewSet,
+):
 
     serializer_class = serializers.PlaylistTrackSerializer
-    queryset = (models.PlaylistTrack.objects.all().for_nested_serialization())
+    queryset = models.PlaylistTrack.objects.all().for_nested_serialization()
     permission_classes = [
         permissions.ConditionalAuthentication,
         permissions.OwnerPermission,
         IsAuthenticatedOrReadOnly,
     ]
-    owner_field = 'playlist.user'
-    owner_checks = ['write']
+    owner_field = "playlist.user"
+    owner_checks = ["write"]
 
     def get_serializer_class(self):
-        if self.request.method in ['PUT', 'PATCH', 'DELETE', 'POST']:
+        if self.request.method in ["PUT", "PATCH", "DELETE", "POST"]:
             return serializers.PlaylistTrackWriteSerializer
         return self.serializer_class
 
     def get_queryset(self):
         return self.queryset.filter(
             fields.privacy_level_query(
-                self.request.user,
-                lookup_field='playlist__privacy_level'))
+                self.request.user, lookup_field="playlist__privacy_level"
+            )
+        )
 
     def perform_destroy(self, instance):
         instance.delete(update_indexes=True)
diff --git a/api/funkwhale_api/providers/acoustid/__init__.py b/api/funkwhale_api/providers/acoustid/__init__.py
index 69fe058b3f..558a95bb80 100644
--- a/api/funkwhale_api/providers/acoustid/__init__.py
+++ b/api/funkwhale_api/providers/acoustid/__init__.py
@@ -14,14 +14,14 @@ class Client(object):
         results = self.match(file_path=file_path)
         MIN_SCORE_FOR_MATCH = 0.8
         try:
-            rows = results['results']
+            rows = results["results"]
         except KeyError:
             return
         for row in rows:
-            if row['score'] >= MIN_SCORE_FOR_MATCH:
+            if row["score"] >= MIN_SCORE_FOR_MATCH:
                 return row
 
 
 def get_acoustid_client():
     manager = global_preferences_registry.manager()
-    return Client(api_key=manager['providers_acoustid__api_key'])
+    return Client(api_key=manager["providers_acoustid__api_key"])
diff --git a/api/funkwhale_api/providers/acoustid/dynamic_preferences_registry.py b/api/funkwhale_api/providers/acoustid/dynamic_preferences_registry.py
index 33c9643b00..2e5a35f0db 100644
--- a/api/funkwhale_api/providers/acoustid/dynamic_preferences_registry.py
+++ b/api/funkwhale_api/providers/acoustid/dynamic_preferences_registry.py
@@ -3,17 +3,15 @@ from django import forms
 from dynamic_preferences.types import StringPreference, Section
 from dynamic_preferences.registries import global_preferences_registry
 
-acoustid = Section('providers_acoustid')
+acoustid = Section("providers_acoustid")
 
 
 @global_preferences_registry.register
 class APIKey(StringPreference):
     section = acoustid
-    name = 'api_key'
-    default = ''
-    verbose_name = 'Acoustid API key'
-    help_text = 'The API key used to query AcoustID. Get one at https://acoustid.org/new-application.'
+    name = "api_key"
+    default = ""
+    verbose_name = "Acoustid API key"
+    help_text = "The API key used to query AcoustID. Get one at https://acoustid.org/new-application."
     widget = forms.PasswordInput
-    field_kwargs = {
-        'required': False,
-    }
+    field_kwargs = {"required": False}
diff --git a/api/funkwhale_api/providers/audiofile/management/commands/import_files.py b/api/funkwhale_api/providers/audiofile/management/commands/import_files.py
index 70ff90ffac..9114375e07 100644
--- a/api/funkwhale_api/providers/audiofile/management/commands/import_files.py
+++ b/api/funkwhale_api/providers/audiofile/management/commands/import_files.py
@@ -11,186 +11,195 @@ from funkwhale_api.users.models import User
 
 
 class Command(BaseCommand):
-    help = 'Import audio files mathinc given glob pattern'
+    help = "Import audio files mathinc given glob pattern"
 
     def add_arguments(self, parser):
-        parser.add_argument('path', type=str)
+        parser.add_argument("path", type=str)
         parser.add_argument(
-            '--recursive',
-            action='store_true',
-            dest='recursive',
+            "--recursive",
+            action="store_true",
+            dest="recursive",
             default=False,
-            help='Will match the pattern recursively (including subdirectories)',
+            help="Will match the pattern recursively (including subdirectories)",
         )
         parser.add_argument(
-            '--username',
-            dest='username',
-            help='The username of the user you want to be bound to the import',
+            "--username",
+            dest="username",
+            help="The username of the user you want to be bound to the import",
         )
         parser.add_argument(
-            '--async',
-            action='store_true',
-            dest='async',
+            "--async",
+            action="store_true",
+            dest="async",
             default=False,
-            help='Will launch celery tasks for each file to import instead of doing it synchronously and block the CLI',
+            help="Will launch celery tasks for each file to import instead of doing it synchronously and block the CLI",
         )
         parser.add_argument(
-            '--exit', '-x',
-            action='store_true',
-            dest='exit_on_failure',
+            "--exit",
+            "-x",
+            action="store_true",
+            dest="exit_on_failure",
             default=False,
-            help='Use this flag to disable error catching',
+            help="Use this flag to disable error catching",
         )
         parser.add_argument(
-            '--in-place', '-i',
-            action='store_true',
-            dest='in_place',
+            "--in-place",
+            "-i",
+            action="store_true",
+            dest="in_place",
             default=False,
             help=(
-                'Import files without duplicating them into the media directory.'
-                'For in-place import to work, the music files must be readable'
-                'by the web-server and funkwhale api and celeryworker processes.'
-                'You may want to use this if you have a big music library to '
-                'import and not much disk space available.'
-            )
+                "Import files without duplicating them into the media directory."
+                "For in-place import to work, the music files must be readable"
+                "by the web-server and funkwhale api and celeryworker processes."
+                "You may want to use this if you have a big music library to "
+                "import and not much disk space available."
+            ),
         )
         parser.add_argument(
-            '--noinput', '--no-input', action='store_false', dest='interactive',
+            "--noinput",
+            "--no-input",
+            action="store_false",
+            dest="interactive",
             help="Do NOT prompt the user for input of any kind.",
         )
 
     def handle(self, *args, **options):
         glob_kwargs = {}
-        if options['recursive']:
-            glob_kwargs['recursive'] = True
+        if options["recursive"]:
+            glob_kwargs["recursive"] = True
         try:
-            matching = sorted(glob.glob(options['path'], **glob_kwargs))
+            matching = sorted(glob.glob(options["path"], **glob_kwargs))
         except TypeError:
-            raise Exception('You need Python 3.5 to use the --recursive flag')
+            raise Exception("You need Python 3.5 to use the --recursive flag")
 
-        if options['in_place']:
+        if options["in_place"]:
             self.stdout.write(
-                'Checking imported paths against settings.MUSIC_DIRECTORY_PATH')
+                "Checking imported paths against settings.MUSIC_DIRECTORY_PATH"
+            )
             p = settings.MUSIC_DIRECTORY_PATH
             if not p:
                 raise CommandError(
-                    'Importing in-place requires setting the '
-                    'MUSIC_DIRECTORY_PATH variable')
+                    "Importing in-place requires setting the "
+                    "MUSIC_DIRECTORY_PATH variable"
+                )
             for m in matching:
                 if not m.startswith(p):
                     raise CommandError(
-                        'Importing in-place only works if importing'
-                        'from {} (MUSIC_DIRECTORY_PATH), as this directory'
-                        'needs to be accessible by the webserver.'
-                        'Culprit: {}'.format(p, m))
+                        "Importing in-place only works if importing"
+                        "from {} (MUSIC_DIRECTORY_PATH), as this directory"
+                        "needs to be accessible by the webserver."
+                        "Culprit: {}".format(p, m)
+                    )
         if not matching:
-            raise CommandError('No file matching pattern, aborting')
+            raise CommandError("No file matching pattern, aborting")
 
         user = None
-        if options['username']:
+        if options["username"]:
             try:
-                user = User.objects.get(username=options['username'])
+                user = User.objects.get(username=options["username"])
             except User.DoesNotExist:
-                raise CommandError('Invalid username')
+                raise CommandError("Invalid username")
         else:
             # we bind the import to the first registered superuser
             try:
-                user = User.objects.filter(is_superuser=True).order_by('pk').first()
+                user = User.objects.filter(is_superuser=True).order_by("pk").first()
                 assert user is not None
             except AssertionError:
                 raise CommandError(
-                    'No superuser available, please provide a --username')
+                    "No superuser available, please provide a --username"
+                )
 
         filtered = self.filter_matching(matching, options)
-        self.stdout.write('Import summary:')
-        self.stdout.write('- {} files found matching this pattern: {}'.format(
-            len(matching), options['path']))
-        self.stdout.write('- {} files already found in database'.format(
-            len(filtered['skipped'])))
-        self.stdout.write('- {} new files'.format(
-            len(filtered['new'])))
-
-        self.stdout.write('Selected options: {}'.format(', '.join([
-            'in place' if options['in_place'] else 'copy music files',
-        ])))
-        if len(filtered['new']) == 0:
-            self.stdout.write('Nothing new to import, exiting')
+        self.stdout.write("Import summary:")
+        self.stdout.write(
+            "- {} files found matching this pattern: {}".format(
+                len(matching), options["path"]
+            )
+        )
+        self.stdout.write(
+            "- {} files already found in database".format(len(filtered["skipped"]))
+        )
+        self.stdout.write("- {} new files".format(len(filtered["new"])))
+
+        self.stdout.write(
+            "Selected options: {}".format(
+                ", ".join(["in place" if options["in_place"] else "copy music files"])
+            )
+        )
+        if len(filtered["new"]) == 0:
+            self.stdout.write("Nothing new to import, exiting")
             return
 
-        if options['interactive']:
+        if options["interactive"]:
             message = (
-                'Are you sure you want to do this?\n\n'
+                "Are you sure you want to do this?\n\n"
                 "Type 'yes' to continue, or 'no' to cancel: "
             )
-            if input(''.join(message)) != 'yes':
+            if input("".join(message)) != "yes":
                 raise CommandError("Import cancelled.")
 
-        batch, errors = self.do_import(
-            filtered['new'], user=user, options=options)
-        message = 'Successfully imported {} tracks'
-        if options['async']:
-            message = 'Successfully launched import for {} tracks'
+        batch, errors = self.do_import(filtered["new"], user=user, options=options)
+        message = "Successfully imported {} tracks"
+        if options["async"]:
+            message = "Successfully launched import for {} tracks"
 
-        self.stdout.write(message.format(len(filtered['new'])))
+        self.stdout.write(message.format(len(filtered["new"])))
         if len(errors) > 0:
-            self.stderr.write(
-                '{} tracks could not be imported:'.format(len(errors)))
+            self.stderr.write("{} tracks could not be imported:".format(len(errors)))
 
             for path, error in errors:
-                self.stderr.write('- {}: {}'.format(path, error))
+                self.stderr.write("- {}: {}".format(path, error))
         self.stdout.write(
-            "For details, please refer to import batch #{}".format(batch.pk))
+            "For details, please refer to import batch #{}".format(batch.pk)
+        )
 
     def filter_matching(self, matching, options):
-        sources = ['file://{}'.format(p) for p in matching]
+        sources = ["file://{}".format(p) for p in matching]
         # we skip reimport for path that are already found
         # as a TrackFile.source
         existing = models.TrackFile.objects.filter(source__in=sources)
-        existing = existing.values_list('source', flat=True)
-        existing = set([p.replace('file://', '', 1) for p in existing])
+        existing = existing.values_list("source", flat=True)
+        existing = set([p.replace("file://", "", 1) for p in existing])
         skipped = set(matching) & existing
         result = {
-            'initial': matching,
-            'skipped': list(sorted(skipped)),
-            'new': list(sorted(set(matching) - skipped)),
+            "initial": matching,
+            "skipped": list(sorted(skipped)),
+            "new": list(sorted(set(matching) - skipped)),
         }
         return result
 
     def do_import(self, paths, user, options):
-        message = '{i}/{total} Importing {path}...'
-        if options['async']:
-            message = '{i}/{total} Launching import for {path}...'
+        message = "{i}/{total} Importing {path}..."
+        if options["async"]:
+            message = "{i}/{total} Launching import for {path}..."
 
         # we create an import batch binded to the user
-        async = options['async']
+        async = options["async"]
         import_handler = tasks.import_job_run.delay if async else tasks.import_job_run
-        batch = user.imports.create(source='shell')
+        batch = user.imports.create(source="shell")
         total = len(paths)
         errors = []
         for i, path in list(enumerate(paths)):
             try:
-                self.stdout.write(
-                    message.format(path=path, i=i+1, total=len(paths)))
+                self.stdout.write(message.format(path=path, i=i + 1, total=len(paths)))
                 self.import_file(path, batch, import_handler, options)
             except Exception as e:
-                if options['exit_on_failure']:
+                if options["exit_on_failure"]:
                     raise
-                m = 'Error while importing {}: {} {}'.format(
-                    path, e.__class__.__name__, e)
+                m = "Error while importing {}: {} {}".format(
+                    path, e.__class__.__name__, e
+                )
                 self.stderr.write(m)
-                errors.append((path, '{} {}'.format(e.__class__.__name__, e)))
+                errors.append((path, "{} {}".format(e.__class__.__name__, e)))
         return batch, errors
 
     def import_file(self, path, batch, import_handler, options):
-        job = batch.jobs.create(
-            source='file://' + path,
-        )
-        if not options['in_place']:
+        job = batch.jobs.create(source="file://" + path)
+        if not options["in_place"]:
             name = os.path.basename(path)
-            with open(path, 'rb') as f:
+            with open(path, "rb") as f:
                 job.audio_file.save(name, File(f))
 
             job.save()
-        import_handler(
-            import_job_id=job.pk,
-            use_acoustid=False)
+        import_handler(import_job_id=job.pk, use_acoustid=False)
diff --git a/api/funkwhale_api/providers/audiofile/tasks.py b/api/funkwhale_api/providers/audiofile/tasks.py
index fb63067355..afd54dc24b 100644
--- a/api/funkwhale_api/providers/audiofile/tasks.py
+++ b/api/funkwhale_api/providers/audiofile/tasks.py
@@ -13,50 +13,40 @@ from funkwhale_api.music import models, metadata
 def import_track_data_from_path(path):
     data = metadata.Metadata(path)
     album = None
-    track_mbid = data.get('musicbrainz_recordingid', None)
-    album_mbid = data.get('musicbrainz_albumid', None)
+    track_mbid = data.get("musicbrainz_recordingid", None)
+    album_mbid = data.get("musicbrainz_albumid", None)
 
     if album_mbid and track_mbid:
         # to gain performance and avoid additional mb lookups,
         # we import from the release data, which is already cached
-        return models.Track.get_or_create_from_release(
-            album_mbid, track_mbid)[0]
+        return models.Track.get_or_create_from_release(album_mbid, track_mbid)[0]
     elif track_mbid:
         return models.Track.get_or_create_from_api(track_mbid)[0]
     elif album_mbid:
         album = models.Album.get_or_create_from_api(album_mbid)[0]
 
     artist = album.artist if album else None
-    artist_mbid = data.get('musicbrainz_artistid', None)
+    artist_mbid = data.get("musicbrainz_artistid", None)
     if not artist:
         if artist_mbid:
             artist = models.Artist.get_or_create_from_api(artist_mbid)[0]
         else:
             artist = models.Artist.objects.get_or_create(
-                name__iexact=data.get('artist'),
-                defaults={
-                    'name': data.get('artist'),
-                },
+                name__iexact=data.get("artist"), defaults={"name": data.get("artist")}
             )[0]
 
-    release_date = data.get('date', default=None)
+    release_date = data.get("date", default=None)
     if not album:
         album = models.Album.objects.get_or_create(
-            title__iexact=data.get('album'),
+            title__iexact=data.get("album"),
             artist=artist,
-            defaults={
-                'title': data.get('album'),
-                'release_date': release_date,
-            },
+            defaults={"title": data.get("album"), "release_date": release_date},
         )[0]
-    position = data.get('track_number', default=None)
+    position = data.get("track_number", default=None)
     track = models.Track.objects.get_or_create(
-        title__iexact=data.get('title'),
+        title__iexact=data.get("title"),
         album=album,
-        defaults={
-            'title': data.get('title'),
-            'position': position,
-        },
+        defaults={"title": data.get("title"), "position": position},
     )[0]
     return track
 
@@ -65,31 +55,27 @@ def import_metadata_with_musicbrainz(path):
     pass
 
 
-@celery.app.task(name='audiofile.from_path')
+@celery.app.task(name="audiofile.from_path")
 def from_path(path):
     acoustid_track_id = None
     try:
         client = get_acoustid_client()
         result = client.get_best_match(path)
-        acoustid_track_id = result['id']
+        acoustid_track_id = result["id"]
     except acoustid.WebServiceError:
         track = import_track_data_from_path(path)
     except (TypeError, KeyError):
         track = import_metadata_without_musicbrainz(path)
     else:
         track, created = models.Track.get_or_create_from_api(
-            mbid=result['recordings'][0]['id']
+            mbid=result["recordings"][0]["id"]
         )
 
     if track.files.count() > 0:
-        raise ValueError('File already exists for track {}'.format(track.pk))
+        raise ValueError("File already exists for track {}".format(track.pk))
 
-    track_file = models.TrackFile(
-        track=track, acoustid_track_id=acoustid_track_id)
-    track_file.audio_file.save(
-        os.path.basename(path),
-        File(open(path, 'rb'))
-    )
+    track_file = models.TrackFile(track=track, acoustid_track_id=acoustid_track_id)
+    track_file.audio_file.save(os.path.basename(path), File(open(path, "rb")))
     track_file.save()
 
     return track_file
diff --git a/api/funkwhale_api/providers/urls.py b/api/funkwhale_api/providers/urls.py
index 10975da53b..6d001ed9db 100644
--- a/api/funkwhale_api/providers/urls.py
+++ b/api/funkwhale_api/providers/urls.py
@@ -2,10 +2,16 @@ from django.conf.urls import include, url
 from funkwhale_api.music import views
 
 urlpatterns = [
-    url(r'^youtube/', include(
-        ('funkwhale_api.providers.youtube.urls', 'youtube'),
-        namespace='youtube')),
-    url(r'^musicbrainz/', include(
-        ('funkwhale_api.musicbrainz.urls', 'musicbrainz'),
-        namespace='musicbrainz')),
+    url(
+        r"^youtube/",
+        include(
+            ("funkwhale_api.providers.youtube.urls", "youtube"), namespace="youtube"
+        ),
+    ),
+    url(
+        r"^musicbrainz/",
+        include(
+            ("funkwhale_api.musicbrainz.urls", "musicbrainz"), namespace="musicbrainz"
+        ),
+    ),
 ]
diff --git a/api/funkwhale_api/providers/youtube/client.py b/api/funkwhale_api/providers/youtube/client.py
index 792e501d74..fbc32efa49 100644
--- a/api/funkwhale_api/providers/youtube/client.py
+++ b/api/funkwhale_api/providers/youtube/client.py
@@ -4,12 +4,11 @@ from apiclient.discovery import build
 from apiclient.errors import HttpError
 from oauth2client.tools import argparser
 
-from dynamic_preferences.registries import (
-    global_preferences_registry as registry)
+from dynamic_preferences.registries import global_preferences_registry as registry
 
 YOUTUBE_API_SERVICE_NAME = "youtube"
 YOUTUBE_API_VERSION = "v3"
-VIDEO_BASE_URL = 'https://www.youtube.com/watch?v={0}'
+VIDEO_BASE_URL = "https://www.youtube.com/watch?v={0}"
 
 
 def _do_search(query):
@@ -17,23 +16,21 @@ def _do_search(query):
     youtube = build(
         YOUTUBE_API_SERVICE_NAME,
         YOUTUBE_API_VERSION,
-        developerKey=manager['providers_youtube__api_key'])
+        developerKey=manager["providers_youtube__api_key"],
+    )
 
-    return youtube.search().list(
-        q=query,
-        part="id,snippet",
-        maxResults=25
-    ).execute()
+    return youtube.search().list(q=query, part="id,snippet", maxResults=25).execute()
 
 
 class Client(object):
-
     def search(self, query):
         search_response = _do_search(query)
         videos = []
         for search_result in search_response.get("items", []):
             if search_result["id"]["kind"] == "youtube#video":
-                search_result['full_url'] = VIDEO_BASE_URL.format(search_result["id"]['videoId'])
+                search_result["full_url"] = VIDEO_BASE_URL.format(
+                    search_result["id"]["videoId"]
+                )
                 videos.append(search_result)
         return videos
 
@@ -44,7 +41,7 @@ class Client(object):
             results[key] = self.search(query)
 
         threads = [
-            threading.Thread(target=search, args=(key, query,))
+            threading.Thread(target=search, args=(key, query))
             for key, query in queries.items()
         ]
         for thread in threads:
@@ -71,16 +68,16 @@ class Client(object):
         }
         """
         return {
-            'id': result['id']['videoId'],
-            'url': 'https://www.youtube.com/watch?v={}'.format(
-                result['id']['videoId']),
-            'type': result['id']['kind'],
-            'title': result['snippet']['title'],
-            'description': result['snippet']['description'],
-            'channelId': result['snippet']['channelId'],
-            'channelTitle': result['snippet']['channelTitle'],
-            'publishedAt': result['snippet']['publishedAt'],
-            'cover': result['snippet']['thumbnails']['high']['url'],
+            "id": result["id"]["videoId"],
+            "url": "https://www.youtube.com/watch?v={}".format(result["id"]["videoId"]),
+            "type": result["id"]["kind"],
+            "title": result["snippet"]["title"],
+            "description": result["snippet"]["description"],
+            "channelId": result["snippet"]["channelId"],
+            "channelTitle": result["snippet"]["channelTitle"],
+            "publishedAt": result["snippet"]["publishedAt"],
+            "cover": result["snippet"]["thumbnails"]["high"]["url"],
         }
 
+
 client = Client()
diff --git a/api/funkwhale_api/providers/youtube/dynamic_preferences_registry.py b/api/funkwhale_api/providers/youtube/dynamic_preferences_registry.py
index ac5fc4bde2..42b54b5350 100644
--- a/api/funkwhale_api/providers/youtube/dynamic_preferences_registry.py
+++ b/api/funkwhale_api/providers/youtube/dynamic_preferences_registry.py
@@ -3,17 +3,15 @@ from django import forms
 from dynamic_preferences.types import StringPreference, Section
 from dynamic_preferences.registries import global_preferences_registry
 
-youtube = Section('providers_youtube')
+youtube = Section("providers_youtube")
 
 
 @global_preferences_registry.register
 class APIKey(StringPreference):
     section = youtube
-    name = 'api_key'
-    default = 'CHANGEME'
-    verbose_name = 'YouTube API key'
-    help_text = 'The API key used to query YouTube. Get one at https://console.developers.google.com/.'
+    name = "api_key"
+    default = "CHANGEME"
+    verbose_name = "YouTube API key"
+    help_text = "The API key used to query YouTube. Get one at https://console.developers.google.com/."
     widget = forms.PasswordInput
-    field_kwargs = {
-        'required': False,
-    }
+    field_kwargs = {"required": False}
diff --git a/api/funkwhale_api/providers/youtube/urls.py b/api/funkwhale_api/providers/youtube/urls.py
index 243d2b8527..c496a2c69d 100644
--- a/api/funkwhale_api/providers/youtube/urls.py
+++ b/api/funkwhale_api/providers/youtube/urls.py
@@ -3,6 +3,6 @@ from .views import APISearch, APISearchs
 
 
 urlpatterns = [
-    url(r'^search/$', APISearch.as_view(), name='search'),
-    url(r'^searchs/$', APISearchs.as_view(), name='searchs'),
+    url(r"^search/$", APISearch.as_view(), name="search"),
+    url(r"^searchs/$", APISearchs.as_view(), name="searchs"),
 ]
diff --git a/api/funkwhale_api/providers/youtube/views.py b/api/funkwhale_api/providers/youtube/views.py
index 989b33090c..012b1825d5 100644
--- a/api/funkwhale_api/providers/youtube/views.py
+++ b/api/funkwhale_api/providers/youtube/views.py
@@ -9,11 +9,8 @@ class APISearch(APIView):
     permission_classes = [ConditionalAuthentication]
 
     def get(self, request, *args, **kwargs):
-        results = client.search(request.GET['query'])
-        return Response([
-            client.to_funkwhale(result)
-            for result in results
-        ])
+        results = client.search(request.GET["query"])
+        return Response([client.to_funkwhale(result) for result in results])
 
 
 class APISearchs(APIView):
@@ -21,10 +18,9 @@ class APISearchs(APIView):
 
     def post(self, request, *args, **kwargs):
         results = client.search_multiple(request.data)
-        return Response({
-            key: [
-                client.to_funkwhale(result)
-                for result in group
-            ]
-            for key, group in results.items()
-        })
+        return Response(
+            {
+                key: [client.to_funkwhale(result) for result in group]
+                for key, group in results.items()
+            }
+        )
diff --git a/api/funkwhale_api/radios/admin.py b/api/funkwhale_api/radios/admin.py
index 6d5abadaff..187950aeb2 100644
--- a/api/funkwhale_api/radios/admin.py
+++ b/api/funkwhale_api/radios/admin.py
@@ -5,44 +5,28 @@ from . import models
 
 @admin.register(models.Radio)
 class RadioAdmin(admin.ModelAdmin):
-    list_display = [
-        'user', 'name', 'is_public', 'creation_date', 'config']
-    list_select_related = [
-        'user',
-    ]
-    list_filter = [
-        'is_public',
-    ]
-    search_fields = ['name', 'description']
+    list_display = ["user", "name", "is_public", "creation_date", "config"]
+    list_select_related = ["user"]
+    list_filter = ["is_public"]
+    search_fields = ["name", "description"]
 
 
 @admin.register(models.RadioSession)
 class RadioSessionAdmin(admin.ModelAdmin):
     list_display = [
-        'user',
-        'custom_radio',
-        'radio_type',
-        'creation_date',
-        'related_object']
-
-    list_select_related = [
-        'user',
-        'custom_radio'
-    ]
-    list_filter = [
-        'radio_type',
+        "user",
+        "custom_radio",
+        "radio_type",
+        "creation_date",
+        "related_object",
     ]
 
+    list_select_related = ["user", "custom_radio"]
+    list_filter = ["radio_type"]
+
 
 @admin.register(models.RadioSessionTrack)
 class RadioSessionTrackAdmin(admin.ModelAdmin):
-    list_display = [
-        'id',
-        'session',
-        'position',
-        'track',]
-
-    list_select_related = [
-        'track',
-        'session'
-    ]
+    list_display = ["id", "session", "position", "track"]
+
+    list_select_related = ["track", "session"]
diff --git a/api/funkwhale_api/radios/factories.py b/api/funkwhale_api/radios/factories.py
index 6a80323bea..69d6ab6e8e 100644
--- a/api/funkwhale_api/radios/factories.py
+++ b/api/funkwhale_api/radios/factories.py
@@ -6,13 +6,13 @@ from funkwhale_api.users.factories import UserFactory
 
 @registry.register
 class RadioFactory(factory.django.DjangoModelFactory):
-    name = factory.Faker('name')
-    description = factory.Faker('paragraphs')
+    name = factory.Faker("name")
+    description = factory.Faker("paragraphs")
     user = factory.SubFactory(UserFactory)
     config = []
 
     class Meta:
-        model = 'radios.Radio'
+        model = "radios.Radio"
 
 
 @registry.register
@@ -20,15 +20,16 @@ class RadioSessionFactory(factory.django.DjangoModelFactory):
     user = factory.SubFactory(UserFactory)
 
     class Meta:
-        model = 'radios.RadioSession'
+        model = "radios.RadioSession"
 
 
-@registry.register(name='radios.CustomRadioSession')
+@registry.register(name="radios.CustomRadioSession")
 class RadioSessionFactory(factory.django.DjangoModelFactory):
     user = factory.SubFactory(UserFactory)
-    radio_type = 'custom'
+    radio_type = "custom"
     custom_radio = factory.SubFactory(
-        RadioFactory, user=factory.SelfAttribute('..user'))
+        RadioFactory, user=factory.SelfAttribute("..user")
+    )
 
     class Meta:
-        model = 'radios.RadioSession'
+        model = "radios.RadioSession"
diff --git a/api/funkwhale_api/radios/filters.py b/api/funkwhale_api/radios/filters.py
index d0d338d663..cb08e46ed2 100644
--- a/api/funkwhale_api/radios/filters.py
+++ b/api/funkwhale_api/radios/filters.py
@@ -11,7 +11,6 @@ from funkwhale_api.taskapp.celery import require_instance
 
 
 class RadioFilterRegistry(persisting_theory.Registry):
-
     def prepare_data(self, data):
         return data()
 
@@ -20,31 +19,27 @@ class RadioFilterRegistry(persisting_theory.Registry):
 
     @property
     def exposed_filters(self):
-        return [
-            f for f in self.values() if f.expose_in_api
-        ]
+        return [f for f in self.values() if f.expose_in_api]
 
 
 registry = RadioFilterRegistry()
 
 
 def run(filters, **kwargs):
-    candidates = kwargs.pop('candidates', models.Track.objects.all())
+    candidates = kwargs.pop("candidates", models.Track.objects.all())
     final_query = None
-    final_query = registry['group'].get_query(
-        candidates, filters=filters, **kwargs)
+    final_query = registry["group"].get_query(candidates, filters=filters, **kwargs)
 
     if final_query:
         candidates = candidates.filter(final_query)
-    return candidates.order_by('pk')
+    return candidates.order_by("pk")
 
 
 def validate(filter_config):
     try:
-        f = registry[filter_config['type']]
+        f = registry[filter_config["type"]]
     except KeyError:
-        raise ValidationError(
-            'Invalid type "{}"'.format(filter_config['type']))
+        raise ValidationError('Invalid type "{}"'.format(filter_config["type"]))
     f.validate(filter_config)
     return True
 
@@ -53,28 +48,22 @@ def test(filter_config, **kwargs):
     """
     Run validation and also gather the candidates for the given config
     """
-    data = {
-        'errors': [],
-        'candidates': {
-            'count': None,
-            'sample': None,
-        }
-    }
+    data = {"errors": [], "candidates": {"count": None, "sample": None}}
     try:
         validate(filter_config)
     except ValidationError as e:
-        data['errors'] = [e.message]
+        data["errors"] = [e.message]
         return data
 
     candidates = run([filter_config], **kwargs)
-    data['candidates']['count'] = candidates.count()
-    data['candidates']['sample'] = candidates[:10]
+    data["candidates"]["count"] = candidates.count()
+    data["candidates"]["sample"] = candidates[:10]
 
     return data
 
 
 def clean_config(filter_config):
-    f = registry[filter_config['type']]
+    f = registry[filter_config["type"]]
     return f.clean_config(filter_config)
 
 
@@ -91,74 +80,75 @@ class RadioFilter(object):
         return filter_config
 
     def validate(self, config):
-        operator = config.get('operator', 'and')
+        operator = config.get("operator", "and")
         try:
-            assert operator in ['or', 'and']
+            assert operator in ["or", "and"]
         except AssertionError:
-            raise ValidationError(
-                'Invalid operator "{}"'.format(config['operator']))
+            raise ValidationError('Invalid operator "{}"'.format(config["operator"]))
 
 
 @registry.register
 class GroupFilter(RadioFilter):
-    code = 'group'
+    code = "group"
     expose_in_api = False
+
     def get_query(self, candidates, filters, **kwargs):
         if not filters:
             return
 
         final_query = None
         for filter_config in filters:
-            f = registry[filter_config['type']]
+            f = registry[filter_config["type"]]
             conf = collections.ChainMap(filter_config, kwargs)
             query = f.get_query(candidates, **conf)
-            if filter_config.get('not', False):
+            if filter_config.get("not", False):
                 query = ~query
 
             if not final_query:
                 final_query = query
             else:
-                operator = filter_config.get('operator', 'and')
-                if operator == 'and':
+                operator = filter_config.get("operator", "and")
+                if operator == "and":
                     final_query &= query
-                elif operator == 'or':
+                elif operator == "or":
                     final_query |= query
                 else:
-                    raise ValueError(
-                        'Invalid query operator "{}"'.format(operator))
+                    raise ValueError('Invalid query operator "{}"'.format(operator))
         return final_query
 
     def validate(self, config):
         super().validate(config)
-        for fc in config['filters']:
-            registry[fc['type']].validate(fc)
+        for fc in config["filters"]:
+            registry[fc["type"]].validate(fc)
 
 
 @registry.register
 class ArtistFilter(RadioFilter):
-    code = 'artist'
-    label = 'Artist'
-    help_text = 'Select tracks for a given artist'
+    code = "artist"
+    label = "Artist"
+    help_text = "Select tracks for a given artist"
     fields = [
         {
-            'name': 'ids',
-            'type': 'list',
-            'subtype': 'number',
-            'autocomplete': reverse_lazy('api:v1:artists-list'),
-            'autocomplete_qs': 'q={query}',
-            'autocomplete_fields': {'name': 'name', 'value': 'id'},
-            'label': 'Artist',
-            'placeholder': 'Select artists'
+            "name": "ids",
+            "type": "list",
+            "subtype": "number",
+            "autocomplete": reverse_lazy("api:v1:artists-list"),
+            "autocomplete_qs": "q={query}",
+            "autocomplete_fields": {"name": "name", "value": "id"},
+            "label": "Artist",
+            "placeholder": "Select artists",
         }
     ]
 
     def clean_config(self, filter_config):
         filter_config = super().clean_config(filter_config)
-        filter_config['ids'] = sorted(filter_config['ids'])
-        names = models.Artist.objects.filter(
-            pk__in=filter_config['ids']
-        ).order_by('id').values_list('name', flat=True)
-        filter_config['names'] = list(names)
+        filter_config["ids"] = sorted(filter_config["ids"])
+        names = (
+            models.Artist.objects.filter(pk__in=filter_config["ids"])
+            .order_by("id")
+            .values_list("name", flat=True)
+        )
+        filter_config["names"] = list(names)
         return filter_config
 
     def get_query(self, candidates, ids, **kwargs):
@@ -167,35 +157,39 @@ class ArtistFilter(RadioFilter):
     def validate(self, config):
         super().validate(config)
         try:
-            pks = models.Artist.objects.filter(
-                pk__in=config['ids']).values_list('pk', flat=True)
-            diff = set(config['ids']) - set(pks)
+            pks = models.Artist.objects.filter(pk__in=config["ids"]).values_list(
+                "pk", flat=True
+            )
+            diff = set(config["ids"]) - set(pks)
             assert len(diff) == 0
         except KeyError:
-            raise ValidationError('You must provide an id')
+            raise ValidationError("You must provide an id")
         except AssertionError:
-            raise ValidationError(
-                'No artist matching ids "{}"'.format(diff))
+            raise ValidationError('No artist matching ids "{}"'.format(diff))
 
 
 @registry.register
 class TagFilter(RadioFilter):
-    code = 'tag'
+    code = "tag"
     fields = [
         {
-            'name': 'names',
-            'type': 'list',
-            'subtype': 'string',
-            'autocomplete': reverse_lazy('api:v1:tags-list'),
-            'autocomplete_qs': '',
-            'autocomplete_fields': {'remoteValues': 'results', 'name': 'name', 'value': 'slug'},
-            'autocomplete_qs': 'query={query}',
-            'label': 'Tags',
-            'placeholder': 'Select tags'
+            "name": "names",
+            "type": "list",
+            "subtype": "string",
+            "autocomplete": reverse_lazy("api:v1:tags-list"),
+            "autocomplete_qs": "",
+            "autocomplete_fields": {
+                "remoteValues": "results",
+                "name": "name",
+                "value": "slug",
+            },
+            "autocomplete_qs": "query={query}",
+            "label": "Tags",
+            "placeholder": "Select tags",
         }
     ]
-    help_text = 'Select tracks with a given tag'
-    label = 'Tag'
+    help_text = "Select tracks with a given tag"
+    label = "Tag"
 
     def get_query(self, candidates, names, **kwargs):
         return Q(tags__slug__in=names)
diff --git a/api/funkwhale_api/radios/filtersets.py b/api/funkwhale_api/radios/filtersets.py
index 49f471373e..d8d7c9ed09 100644
--- a/api/funkwhale_api/radios/filtersets.py
+++ b/api/funkwhale_api/radios/filtersets.py
@@ -4,9 +4,6 @@ from . import models
 
 
 class RadioFilter(django_filters.FilterSet):
-
     class Meta:
         model = models.Radio
-        fields = {
-            'name': ['exact', 'iexact', 'startswith', 'icontains']
-        }
+        fields = {"name": ["exact", "iexact", "startswith", "icontains"]}
diff --git a/api/funkwhale_api/radios/migrations/0001_initial.py b/api/funkwhale_api/radios/migrations/0001_initial.py
index 46faf749ed..912da7be3b 100644
--- a/api/funkwhale_api/radios/migrations/0001_initial.py
+++ b/api/funkwhale_api/radios/migrations/0001_initial.py
@@ -10,33 +10,72 @@ class Migration(migrations.Migration):
 
     dependencies = [
         migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-        ('music', '0004_track_tags'),
+        ("music", "0004_track_tags"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='RadioSession',
+            name="RadioSession",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
-                ('radio_type', models.CharField(max_length=50)),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('user', models.ForeignKey(related_name='radio_sessions', blank=True, to=settings.AUTH_USER_MODEL, null=True, on_delete=models.CASCADE)),
+                (
+                    "id",
+                    models.AutoField(
+                        verbose_name="ID",
+                        primary_key=True,
+                        serialize=False,
+                        auto_created=True,
+                    ),
+                ),
+                ("radio_type", models.CharField(max_length=50)),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                (
+                    "user",
+                    models.ForeignKey(
+                        related_name="radio_sessions",
+                        blank=True,
+                        to=settings.AUTH_USER_MODEL,
+                        null=True,
+                        on_delete=models.CASCADE,
+                    ),
+                ),
             ],
         ),
         migrations.CreateModel(
-            name='RadioSessionTrack',
+            name="RadioSessionTrack",
             fields=[
-                ('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
-                ('position', models.IntegerField(default=1)),
-                ('session', models.ForeignKey(to='radios.RadioSession', related_name='session_tracks', on_delete=models.CASCADE)),
-                ('track', models.ForeignKey(to='music.Track', related_name='radio_session_tracks', on_delete=models.CASCADE)),
+                (
+                    "id",
+                    models.AutoField(
+                        verbose_name="ID",
+                        primary_key=True,
+                        serialize=False,
+                        auto_created=True,
+                    ),
+                ),
+                ("position", models.IntegerField(default=1)),
+                (
+                    "session",
+                    models.ForeignKey(
+                        to="radios.RadioSession",
+                        related_name="session_tracks",
+                        on_delete=models.CASCADE,
+                    ),
+                ),
+                (
+                    "track",
+                    models.ForeignKey(
+                        to="music.Track",
+                        related_name="radio_session_tracks",
+                        on_delete=models.CASCADE,
+                    ),
+                ),
             ],
-            options={
-                'ordering': ('session', 'position'),
-            },
+            options={"ordering": ("session", "position")},
         ),
         migrations.AlterUniqueTogether(
-            name='radiosessiontrack',
-            unique_together=set([('session', 'position')]),
+            name="radiosessiontrack", unique_together=set([("session", "position")])
         ),
     ]
diff --git a/api/funkwhale_api/radios/migrations/0002_radiosession_session_key.py b/api/funkwhale_api/radios/migrations/0002_radiosession_session_key.py
index a903ae3ea8..6c206aa625 100644
--- a/api/funkwhale_api/radios/migrations/0002_radiosession_session_key.py
+++ b/api/funkwhale_api/radios/migrations/0002_radiosession_session_key.py
@@ -6,14 +6,12 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('radios', '0001_initial'),
-    ]
+    dependencies = [("radios", "0001_initial")]
 
     operations = [
         migrations.AddField(
-            model_name='radiosession',
-            name='session_key',
+            model_name="radiosession",
+            name="session_key",
             field=models.CharField(null=True, blank=True, max_length=100),
-        ),
+        )
     ]
diff --git a/api/funkwhale_api/radios/migrations/0003_auto_20160521_1708.py b/api/funkwhale_api/radios/migrations/0003_auto_20160521_1708.py
index 7c70abc2e1..2af084a871 100644
--- a/api/funkwhale_api/radios/migrations/0003_auto_20160521_1708.py
+++ b/api/funkwhale_api/radios/migrations/0003_auto_20160521_1708.py
@@ -7,19 +7,24 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('contenttypes', '0002_remove_content_type_name'),
-        ('radios', '0002_radiosession_session_key'),
+        ("contenttypes", "0002_remove_content_type_name"),
+        ("radios", "0002_radiosession_session_key"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='radiosession',
-            name='related_object_content_type',
-            field=models.ForeignKey(null=True, to='contenttypes.ContentType', blank=True, on_delete=models.CASCADE),
+            model_name="radiosession",
+            name="related_object_content_type",
+            field=models.ForeignKey(
+                null=True,
+                to="contenttypes.ContentType",
+                blank=True,
+                on_delete=models.CASCADE,
+            ),
         ),
         migrations.AddField(
-            model_name='radiosession',
-            name='related_object_id',
+            model_name="radiosession",
+            name="related_object_id",
             field=models.PositiveIntegerField(blank=True, null=True),
         ),
     ]
diff --git a/api/funkwhale_api/radios/migrations/0004_auto_20180107_1813.py b/api/funkwhale_api/radios/migrations/0004_auto_20180107_1813.py
index fc768b3033..72f2a7d315 100644
--- a/api/funkwhale_api/radios/migrations/0004_auto_20180107_1813.py
+++ b/api/funkwhale_api/radios/migrations/0004_auto_20180107_1813.py
@@ -11,26 +11,52 @@ class Migration(migrations.Migration):
 
     dependencies = [
         migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-        ('radios', '0003_auto_20160521_1708'),
+        ("radios", "0003_auto_20160521_1708"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='Radio',
+            name="Radio",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('name', models.CharField(max_length=100)),
-                ('description', models.TextField(blank=True)),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('is_public', models.BooleanField(default=False)),
-                ('version', models.PositiveIntegerField(default=0)),
-                ('config', django.contrib.postgres.fields.jsonb.JSONField()),
-                ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='radios', to=settings.AUTH_USER_MODEL)),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=100)),
+                ("description", models.TextField(blank=True)),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                ("is_public", models.BooleanField(default=False)),
+                ("version", models.PositiveIntegerField(default=0)),
+                ("config", django.contrib.postgres.fields.jsonb.JSONField()),
+                (
+                    "user",
+                    models.ForeignKey(
+                        blank=True,
+                        null=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="radios",
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
             ],
         ),
         migrations.AddField(
-            model_name='radiosession',
-            name='custom_radio',
-            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sessions', to='radios.Radio'),
+            model_name="radiosession",
+            name="custom_radio",
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.CASCADE,
+                related_name="sessions",
+                to="radios.Radio",
+            ),
         ),
     ]
diff --git a/api/funkwhale_api/radios/models.py b/api/funkwhale_api/radios/models.py
index 8758abc619..af0fa26da2 100644
--- a/api/funkwhale_api/radios/models.py
+++ b/api/funkwhale_api/radios/models.py
@@ -14,11 +14,12 @@ from . import filters
 class Radio(models.Model):
     CONFIG_VERSION = 0
     user = models.ForeignKey(
-        'users.User',
-        related_name='radios',
+        "users.User",
+        related_name="radios",
         null=True,
         blank=True,
-        on_delete=models.CASCADE)
+        on_delete=models.CASCADE,
+    )
     name = models.CharField(max_length=100)
     description = models.TextField(blank=True)
     creation_date = models.DateTimeField(default=timezone.now)
@@ -32,27 +33,25 @@ class Radio(models.Model):
 
 class RadioSession(models.Model):
     user = models.ForeignKey(
-        'users.User',
-        related_name='radio_sessions',
+        "users.User",
+        related_name="radio_sessions",
         null=True,
         blank=True,
-        on_delete=models.CASCADE)
+        on_delete=models.CASCADE,
+    )
     session_key = models.CharField(max_length=100, null=True, blank=True)
     radio_type = models.CharField(max_length=50)
     custom_radio = models.ForeignKey(
-        Radio,
-        related_name='sessions',
-        null=True,
-        blank=True,
-        on_delete=models.CASCADE)
+        Radio, related_name="sessions", null=True, blank=True, on_delete=models.CASCADE
+    )
     creation_date = models.DateTimeField(default=timezone.now)
     related_object_content_type = models.ForeignKey(
-        ContentType,
-        blank=True,
-        null=True,
-        on_delete=models.CASCADE)
+        ContentType, blank=True, null=True, on_delete=models.CASCADE
+    )
     related_object_id = models.PositiveIntegerField(blank=True, null=True)
-    related_object = GenericForeignKey('related_object_content_type', 'related_object_id')
+    related_object = GenericForeignKey(
+        "related_object_content_type", "related_object_id"
+    )
 
     def save(self, **kwargs):
         self.radio.clean(self)
@@ -62,14 +61,16 @@ class RadioSession(models.Model):
     def next_position(self):
         next_position = 1
 
-        last_session_track = self.session_tracks.all().order_by('-position').first()
+        last_session_track = self.session_tracks.all().order_by("-position").first()
         if last_session_track:
             next_position = last_session_track.position + 1
 
         return next_position
 
     def add(self, track):
-        new_session_track = RadioSessionTrack.objects.create(track=track, session=self, position=self.next_position)
+        new_session_track = RadioSessionTrack.objects.create(
+            track=track, session=self, position=self.next_position
+        )
 
         return new_session_track
 
@@ -77,16 +78,19 @@ class RadioSession(models.Model):
     def radio(self):
         from .registries import registry
         from . import radios
+
         return registry[self.radio_type](session=self)
 
 
 class RadioSessionTrack(models.Model):
     session = models.ForeignKey(
-        RadioSession, related_name='session_tracks', on_delete=models.CASCADE)
+        RadioSession, related_name="session_tracks", on_delete=models.CASCADE
+    )
     position = models.IntegerField(default=1)
     track = models.ForeignKey(
-        Track, related_name='radio_session_tracks', on_delete=models.CASCADE)
+        Track, related_name="radio_session_tracks", on_delete=models.CASCADE
+    )
 
     class Meta:
-        ordering = ('session', 'position')
-        unique_together = ('session', 'position')
+        ordering = ("session", "position")
+        unique_together = ("session", "position")
diff --git a/api/funkwhale_api/radios/radios.py b/api/funkwhale_api/radios/radios.py
index 0d045ea4dc..f876d03b28 100644
--- a/api/funkwhale_api/radios/radios.py
+++ b/api/funkwhale_api/radios/radios.py
@@ -12,7 +12,6 @@ from .registries import registry
 
 
 class SimpleRadio(object):
-
     def clean(self, instance):
         return
 
@@ -37,13 +36,13 @@ class SessionRadio(SimpleRadio):
         self.session = session
 
     def start_session(self, user, **kwargs):
-        self.session = models.RadioSession.objects.create(user=user, radio_type=self.radio_type, **kwargs)
+        self.session = models.RadioSession.objects.create(
+            user=user, radio_type=self.radio_type, **kwargs
+        )
         return self.session
 
     def get_queryset(self, **kwargs):
-        qs = Track.objects.annotate(
-            files_count=Count('files')
-        )
+        qs = Track.objects.annotate(files_count=Count("files"))
         return qs.filter(files_count__gt=0)
 
     def get_queryset_kwargs(self):
@@ -57,7 +56,9 @@ class SessionRadio(SimpleRadio):
         return queryset
 
     def filter_from_session(self, queryset):
-        already_played = self.session.session_tracks.all().values_list('track', flat=True)
+        already_played = self.session.session_tracks.all().values_list(
+            "track", flat=True
+        )
         queryset = queryset.exclude(pk__in=already_played)
         return queryset
 
@@ -76,60 +77,51 @@ class SessionRadio(SimpleRadio):
         return data
 
 
-@registry.register(name='random')
+@registry.register(name="random")
 class RandomRadio(SessionRadio):
     def get_queryset(self, **kwargs):
         qs = super().get_queryset(**kwargs)
-        return qs.order_by('?')
+        return qs.order_by("?")
 
 
-@registry.register(name='favorites')
+@registry.register(name="favorites")
 class FavoritesRadio(SessionRadio):
-
     def get_queryset_kwargs(self):
         kwargs = super().get_queryset_kwargs()
         if self.session:
-            kwargs['user'] = self.session.user
+            kwargs["user"] = self.session.user
         return kwargs
 
     def get_queryset(self, **kwargs):
         qs = super().get_queryset(**kwargs)
-        track_ids = kwargs['user'].track_favorites.all().values_list('track', flat=True)
+        track_ids = kwargs["user"].track_favorites.all().values_list("track", flat=True)
         return qs.filter(pk__in=track_ids)
 
 
-@registry.register(name='custom')
+@registry.register(name="custom")
 class CustomRadio(SessionRadio):
-
     def get_queryset_kwargs(self):
         kwargs = super().get_queryset_kwargs()
-        kwargs['user'] = self.session.user
-        kwargs['custom_radio'] = self.session.custom_radio
+        kwargs["user"] = self.session.user
+        kwargs["custom_radio"] = self.session.custom_radio
         return kwargs
 
     def get_queryset(self, **kwargs):
         qs = super().get_queryset(**kwargs)
-        return filters.run(
-            kwargs['custom_radio'].config,
-            candidates=qs,
-        )
+        return filters.run(kwargs["custom_radio"].config, candidates=qs)
 
     def validate_session(self, data, **context):
         data = super().validate_session(data, **context)
         try:
-            user = data['user']
+            user = data["user"]
         except KeyError:
-            user = context['user']
+            user = context["user"]
         try:
-            assert (
-                data['custom_radio'].user == user or
-                data['custom_radio'].is_public)
+            assert data["custom_radio"].user == user or data["custom_radio"].is_public
         except KeyError:
-            raise serializers.ValidationError(
-                'You must provide a custom radio')
+            raise serializers.ValidationError("You must provide a custom radio")
         except AssertionError:
-            raise serializers.ValidationError(
-                "You don't have access to this radio")
+            raise serializers.ValidationError("You don't have access to this radio")
         return data
 
 
@@ -139,15 +131,17 @@ class RelatedObjectRadio(SessionRadio):
     def clean(self, instance):
         super().clean(instance)
         if not instance.related_object:
-            raise ValidationError('Cannot start RelatedObjectRadio without related object')
+            raise ValidationError(
+                "Cannot start RelatedObjectRadio without related object"
+            )
         if not isinstance(instance.related_object, self.model):
-            raise ValidationError('Trying to start radio with bad related object')
+            raise ValidationError("Trying to start radio with bad related object")
 
     def get_related_object(self, pk):
         return self.model.objects.get(pk=pk)
 
 
-@registry.register(name='tag')
+@registry.register(name="tag")
 class TagRadio(RelatedObjectRadio):
     model = Tag
 
@@ -155,7 +149,8 @@ class TagRadio(RelatedObjectRadio):
         qs = super().get_queryset(**kwargs)
         return Track.objects.filter(tags__in=[self.session.related_object])
 
-@registry.register(name='artist')
+
+@registry.register(name="artist")
 class ArtistRadio(RelatedObjectRadio):
     model = Artist
 
@@ -164,7 +159,7 @@ class ArtistRadio(RelatedObjectRadio):
         return qs.filter(artist=self.session.related_object)
 
 
-@registry.register(name='less-listened')
+@registry.register(name="less-listened")
 class LessListenedRadio(RelatedObjectRadio):
     model = User
 
@@ -174,5 +169,5 @@ class LessListenedRadio(RelatedObjectRadio):
 
     def get_queryset(self, **kwargs):
         qs = super().get_queryset(**kwargs)
-        listened = self.session.user.listenings.all().values_list('track', flat=True)
-        return qs.exclude(pk__in=listened).order_by('?')
+        listened = self.session.user.listenings.all().values_list("track", flat=True)
+        return qs.exclude(pk__in=listened).order_by("?")
diff --git a/api/funkwhale_api/radios/registries.py b/api/funkwhale_api/radios/registries.py
index eec223539d..4a30102b79 100644
--- a/api/funkwhale_api/radios/registries.py
+++ b/api/funkwhale_api/radios/registries.py
@@ -1,8 +1,10 @@
 import persisting_theory
 
+
 class RadioRegistry(persisting_theory.Registry):
     def prepare_name(self, data, name=None):
-        setattr(data, 'radio_type', name)
+        setattr(data, "radio_type", name)
         return name
 
-registry =  RadioRegistry()
+
+registry = RadioRegistry()
diff --git a/api/funkwhale_api/radios/serializers.py b/api/funkwhale_api/radios/serializers.py
index 8c59f87156..be60bbc3e5 100644
--- a/api/funkwhale_api/radios/serializers.py
+++ b/api/funkwhale_api/radios/serializers.py
@@ -9,7 +9,7 @@ from .radios import registry
 
 
 class FilterSerializer(serializers.Serializer):
-    type = serializers.CharField(source='code')
+    type = serializers.CharField(source="code")
     label = serializers.CharField()
     help_text = serializers.CharField()
     fields = serializers.ReadOnlyField()
@@ -21,19 +21,20 @@ class RadioSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.Radio
         fields = (
-            'id',
-            'is_public',
-            'name',
-            'creation_date',
-            'user',
-            'config',
-            'description')
-        read_only_fields = ('user', 'creation_date')
+            "id",
+            "is_public",
+            "name",
+            "creation_date",
+            "user",
+            "config",
+            "description",
+        )
+        read_only_fields = ("user", "creation_date")
 
     def save(self, **kwargs):
-        kwargs['config'] = [
-            filters.registry[f['type']].clean_config(f)
-            for f in self.validated_data['config']
+        kwargs["config"] = [
+            filters.registry[f["type"]].clean_config(f)
+            for f in self.validated_data["config"]
         ]
 
         return super().save(**kwargs)
@@ -42,7 +43,7 @@ class RadioSerializer(serializers.ModelSerializer):
 class RadioSessionTrackSerializerCreate(serializers.ModelSerializer):
     class Meta:
         model = models.RadioSessionTrack
-        fields = ('session',)
+        fields = ("session",)
 
 
 class RadioSessionTrackSerializer(serializers.ModelSerializer):
@@ -50,28 +51,30 @@ class RadioSessionTrackSerializer(serializers.ModelSerializer):
 
     class Meta:
         model = models.RadioSessionTrack
-        fields = ('id', 'session', 'position', 'track')
+        fields = ("id", "session", "position", "track")
 
 
 class RadioSessionSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.RadioSession
         fields = (
-            'id',
-            'radio_type',
-            'related_object_id',
-            'user',
-            'creation_date',
-            'custom_radio',
+            "id",
+            "radio_type",
+            "related_object_id",
+            "user",
+            "creation_date",
+            "custom_radio",
         )
 
     def validate(self, data):
-        registry[data['radio_type']]().validate_session(data, **self.context)
+        registry[data["radio_type"]]().validate_session(data, **self.context)
         return data
 
     def create(self, validated_data):
-        validated_data['user'] = self.context['user']
-        if validated_data.get('related_object_id'):
-            radio = registry[validated_data['radio_type']]()
-            validated_data['related_object'] = radio.get_related_object(validated_data['related_object_id'])
+        validated_data["user"] = self.context["user"]
+        if validated_data.get("related_object_id"):
+            radio = registry[validated_data["radio_type"]]()
+            validated_data["related_object"] = radio.get_related_object(
+                validated_data["related_object_id"]
+            )
         return super().create(validated_data)
diff --git a/api/funkwhale_api/radios/urls.py b/api/funkwhale_api/radios/urls.py
index d84615ca57..3f48455b06 100644
--- a/api/funkwhale_api/radios/urls.py
+++ b/api/funkwhale_api/radios/urls.py
@@ -2,10 +2,11 @@ from django.conf.urls import include, url
 from . import views
 
 from rest_framework import routers
+
 router = routers.SimpleRouter()
-router.register(r'sessions', views.RadioSessionViewSet, 'sessions')
-router.register(r'radios', views.RadioViewSet, 'radios')
-router.register(r'tracks', views.RadioSessionTrackViewSet, 'tracks')
+router.register(r"sessions", views.RadioSessionViewSet, "sessions")
+router.register(r"radios", views.RadioViewSet, "radios")
+router.register(r"tracks", views.RadioSessionTrackViewSet, "tracks")
 
 
 urlpatterns = router.urls
diff --git a/api/funkwhale_api/radios/views.py b/api/funkwhale_api/radios/views.py
index ca510b82c4..1d933a2939 100644
--- a/api/funkwhale_api/radios/views.py
+++ b/api/funkwhale_api/radios/views.py
@@ -17,12 +17,13 @@ from . import serializers
 
 
 class RadioViewSet(
-        mixins.CreateModelMixin,
-        mixins.RetrieveModelMixin,
-        mixins.UpdateModelMixin,
-        mixins.ListModelMixin,
-        mixins.DestroyModelMixin,
-        viewsets.GenericViewSet):
+    mixins.CreateModelMixin,
+    mixins.RetrieveModelMixin,
+    mixins.UpdateModelMixin,
+    mixins.ListModelMixin,
+    mixins.DestroyModelMixin,
+    viewsets.GenericViewSet,
+):
 
     serializer_class = serializers.RadioSerializer
     permission_classes = [permissions.IsAuthenticated]
@@ -42,7 +43,7 @@ class RadioViewSet(
             raise Http404
         return serializer.save(user=self.request.user)
 
-    @detail_route(methods=['get'])
+    @detail_route(methods=["get"])
     def tracks(self, request, *args, **kwargs):
         radio = self.get_object()
         tracks = radio.get_candidates().for_nested_serialization()
@@ -52,36 +53,33 @@ class RadioViewSet(
             serializer = TrackSerializer(page, many=True)
             return self.get_paginated_response(serializer.data)
 
-    @list_route(methods=['get'])
+    @list_route(methods=["get"])
     def filters(self, request, *args, **kwargs):
         serializer = serializers.FilterSerializer(
-            filters.registry.exposed_filters, many=True)
+            filters.registry.exposed_filters, many=True
+        )
         return Response(serializer.data)
 
-    @list_route(methods=['post'])
+    @list_route(methods=["post"])
     def validate(self, request, *args, **kwargs):
         try:
-            f_list = request.data['filters']
+            f_list = request.data["filters"]
         except KeyError:
-            return Response(
-                {'error': 'You must provide a filters list'}, status=400)
-        data = {
-            'filters': []
-        }
+            return Response({"error": "You must provide a filters list"}, status=400)
+        data = {"filters": []}
         for f in f_list:
             results = filters.test(f)
-            if results['candidates']['sample']:
-                qs = results['candidates']['sample'].for_nested_serialization()
-                results['candidates']['sample'] = TrackSerializer(
-                    qs, many=True).data
-            data['filters'].append(results)
+            if results["candidates"]["sample"]:
+                qs = results["candidates"]["sample"].for_nested_serialization()
+                results["candidates"]["sample"] = TrackSerializer(qs, many=True).data
+            data["filters"].append(results)
 
         return Response(data)
 
 
-class RadioSessionViewSet(mixins.CreateModelMixin,
-                          mixins.RetrieveModelMixin,
-                          viewsets.GenericViewSet):
+class RadioSessionViewSet(
+    mixins.CreateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet
+):
 
     serializer_class = serializers.RadioSessionSerializer
     queryset = models.RadioSession.objects.all()
@@ -93,12 +91,11 @@ class RadioSessionViewSet(mixins.CreateModelMixin,
 
     def get_serializer_context(self):
         context = super().get_serializer_context()
-        context['user'] = self.request.user
+        context["user"] = self.request.user
         return context
 
 
-class RadioSessionTrackViewSet(mixins.CreateModelMixin,
-                               viewsets.GenericViewSet):
+class RadioSessionTrackViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
     serializer_class = serializers.RadioSessionTrackSerializer
     queryset = models.RadioSessionTrack.objects.all()
     permission_classes = [permissions.IsAuthenticated]
@@ -106,20 +103,24 @@ class RadioSessionTrackViewSet(mixins.CreateModelMixin,
     def create(self, request, *args, **kwargs):
         serializer = self.get_serializer(data=request.data)
         serializer.is_valid(raise_exception=True)
-        session = serializer.validated_data['session']
+        session = serializer.validated_data["session"]
         try:
             assert request.user == session.user
         except AssertionError:
             return Response(status=status.HTTP_403_FORBIDDEN)
         track = session.radio.pick()
-        session_track = session.session_tracks.all().latest('id')
+        session_track = session.session_tracks.all().latest("id")
         # self.perform_create(serializer)
         # dirty override here, since we use a different serializer for creation and detail
-        serializer = self.serializer_class(instance=session_track, context=self.get_serializer_context())
+        serializer = self.serializer_class(
+            instance=session_track, context=self.get_serializer_context()
+        )
         headers = self.get_success_headers(serializer.data)
-        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
+        return Response(
+            serializer.data, status=status.HTTP_201_CREATED, headers=headers
+        )
 
     def get_serializer_class(self, *args, **kwargs):
-        if self.action == 'create':
+        if self.action == "create":
             return serializers.RadioSessionTrackSerializerCreate
         return super().get_serializer_class(*args, **kwargs)
diff --git a/api/funkwhale_api/requests/admin.py b/api/funkwhale_api/requests/admin.py
index 8ca008a03a..b0f1a7990e 100644
--- a/api/funkwhale_api/requests/admin.py
+++ b/api/funkwhale_api/requests/admin.py
@@ -5,11 +5,7 @@ from . import models
 
 @admin.register(models.ImportRequest)
 class ImportRequestAdmin(admin.ModelAdmin):
-    list_display = ['artist_name', 'user', 'status', 'creation_date']
-    list_select_related = [
-        'user'
-    ]
-    list_filter = [
-        'status',
-    ]
-    search_fields = ['artist_name', 'comment', 'albums']
+    list_display = ["artist_name", "user", "status", "creation_date"]
+    list_select_related = ["user"]
+    list_filter = ["status"]
+    search_fields = ["artist_name", "comment", "albums"]
diff --git a/api/funkwhale_api/requests/api_urls.py b/api/funkwhale_api/requests/api_urls.py
index 37459a664a..c7c1a103bd 100644
--- a/api/funkwhale_api/requests/api_urls.py
+++ b/api/funkwhale_api/requests/api_urls.py
@@ -2,10 +2,8 @@ from django.conf.urls import include, url
 from . import views
 
 from rest_framework import routers
+
 router = routers.SimpleRouter()
-router.register(
-    r'import-requests',
-    views.ImportRequestViewSet,
-    'import-requests')
+router.register(r"import-requests", views.ImportRequestViewSet, "import-requests")
 
 urlpatterns = router.urls
diff --git a/api/funkwhale_api/requests/factories.py b/api/funkwhale_api/requests/factories.py
index 2bcdeb6a96..d6673aebdc 100644
--- a/api/funkwhale_api/requests/factories.py
+++ b/api/funkwhale_api/requests/factories.py
@@ -6,10 +6,10 @@ from funkwhale_api.users.factories import UserFactory
 
 @registry.register
 class ImportRequestFactory(factory.django.DjangoModelFactory):
-    artist_name = factory.Faker('name')
-    albums = factory.Faker('sentence')
+    artist_name = factory.Faker("name")
+    albums = factory.Faker("sentence")
     user = factory.SubFactory(UserFactory)
-    comment = factory.Faker('paragraph')
+    comment = factory.Faker("paragraph")
 
     class Meta:
-        model = 'requests.ImportRequest'
+        model = "requests.ImportRequest"
diff --git a/api/funkwhale_api/requests/filters.py b/api/funkwhale_api/requests/filters.py
index 7d06033629..ad8b000d27 100644
--- a/api/funkwhale_api/requests/filters.py
+++ b/api/funkwhale_api/requests/filters.py
@@ -6,17 +6,14 @@ from . import models
 
 class ImportRequestFilter(django_filters.FilterSet):
 
-    q = fields.SearchFilter(search_fields=[
-        'artist_name',
-        'user__username',
-        'albums',
-        'comment',
-    ])
+    q = fields.SearchFilter(
+        search_fields=["artist_name", "user__username", "albums", "comment"]
+    )
 
     class Meta:
         model = models.ImportRequest
         fields = {
-            'artist_name': ['exact', 'iexact', 'startswith', 'icontains'],
-            'status': ['exact'],
-            'user__username': ['exact'],
+            "artist_name": ["exact", "iexact", "startswith", "icontains"],
+            "status": ["exact"],
+            "user__username": ["exact"],
         }
diff --git a/api/funkwhale_api/requests/migrations/0001_initial.py b/api/funkwhale_api/requests/migrations/0001_initial.py
index 7c239b3c07..ab9b619ef5 100644
--- a/api/funkwhale_api/requests/migrations/0001_initial.py
+++ b/api/funkwhale_api/requests/migrations/0001_initial.py
@@ -10,22 +10,50 @@ class Migration(migrations.Migration):
 
     initial = True
 
-    dependencies = [
-        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
-    ]
+    dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
 
     operations = [
         migrations.CreateModel(
-            name='ImportRequest',
+            name="ImportRequest",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('creation_date', models.DateTimeField(default=django.utils.timezone.now)),
-                ('imported_date', models.DateTimeField(blank=True, null=True)),
-                ('artist_name', models.CharField(max_length=250)),
-                ('albums', models.CharField(blank=True, max_length=3000, null=True)),
-                ('status', models.CharField(choices=[('pending', 'pending'), ('accepted', 'accepted'), ('imported', 'imported'), ('closed', 'closed')], default='pending', max_length=50)),
-                ('comment', models.TextField(blank=True, max_length=3000, null=True)),
-                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='import_requests', to=settings.AUTH_USER_MODEL)),
+                (
+                    "id",
+                    models.AutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                (
+                    "creation_date",
+                    models.DateTimeField(default=django.utils.timezone.now),
+                ),
+                ("imported_date", models.DateTimeField(blank=True, null=True)),
+                ("artist_name", models.CharField(max_length=250)),
+                ("albums", models.CharField(blank=True, max_length=3000, null=True)),
+                (
+                    "status",
+                    models.CharField(
+                        choices=[
+                            ("pending", "pending"),
+                            ("accepted", "accepted"),
+                            ("imported", "imported"),
+                            ("closed", "closed"),
+                        ],
+                        default="pending",
+                        max_length=50,
+                    ),
+                ),
+                ("comment", models.TextField(blank=True, max_length=3000, null=True)),
+                (
+                    "user",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE,
+                        related_name="import_requests",
+                        to=settings.AUTH_USER_MODEL,
+                    ),
+                ),
             ],
-        ),
+        )
     ]
diff --git a/api/funkwhale_api/requests/models.py b/api/funkwhale_api/requests/models.py
index d08dd4004f..650d502407 100644
--- a/api/funkwhale_api/requests/models.py
+++ b/api/funkwhale_api/requests/models.py
@@ -2,17 +2,13 @@ from django.db import models
 
 from django.utils import timezone
 
-NATURE_CHOICES = [
-    ('artist', 'artist'),
-    ('album', 'album'),
-    ('track', 'track'),
-]
+NATURE_CHOICES = [("artist", "artist"), ("album", "album"), ("track", "track")]
 
 STATUS_CHOICES = [
-    ('pending', 'pending'),
-    ('accepted', 'accepted'),
-    ('imported', 'imported'),
-    ('closed', 'closed'),
+    ("pending", "pending"),
+    ("accepted", "accepted"),
+    ("imported", "imported"),
+    ("closed", "closed"),
 ]
 
 
@@ -20,11 +16,9 @@ class ImportRequest(models.Model):
     creation_date = models.DateTimeField(default=timezone.now)
     imported_date = models.DateTimeField(null=True, blank=True)
     user = models.ForeignKey(
-        'users.User',
-        related_name='import_requests',
-        on_delete=models.CASCADE)
+        "users.User", related_name="import_requests", on_delete=models.CASCADE
+    )
     artist_name = models.CharField(max_length=250)
     albums = models.CharField(max_length=3000, null=True, blank=True)
-    status = models.CharField(
-        choices=STATUS_CHOICES, max_length=50, default='pending')
+    status = models.CharField(choices=STATUS_CHOICES, max_length=50, default="pending")
     comment = models.TextField(null=True, blank=True, max_length=3000)
diff --git a/api/funkwhale_api/requests/serializers.py b/api/funkwhale_api/requests/serializers.py
index 51a709514e..2a810a9997 100644
--- a/api/funkwhale_api/requests/serializers.py
+++ b/api/funkwhale_api/requests/serializers.py
@@ -11,20 +11,17 @@ class ImportRequestSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.ImportRequest
         fields = (
-            'id',
-            'status',
-            'albums',
-            'artist_name',
-            'user',
-            'creation_date',
-            'imported_date',
-            'comment')
-        read_only_fields = (
-            'creation_date',
-            'imported_date',
-            'user',
-            'status')
+            "id",
+            "status",
+            "albums",
+            "artist_name",
+            "user",
+            "creation_date",
+            "imported_date",
+            "comment",
+        )
+        read_only_fields = ("creation_date", "imported_date", "user", "status")
 
     def create(self, validated_data):
-        validated_data['user'] = self.context['user']
+        validated_data["user"] = self.context["user"]
         return super().create(validated_data)
diff --git a/api/funkwhale_api/requests/views.py b/api/funkwhale_api/requests/views.py
index 6553f3316f..4b68555a87 100644
--- a/api/funkwhale_api/requests/views.py
+++ b/api/funkwhale_api/requests/views.py
@@ -9,18 +9,18 @@ from . import serializers
 
 
 class ImportRequestViewSet(
-        mixins.CreateModelMixin,
-        mixins.RetrieveModelMixin,
-        mixins.ListModelMixin,
-        viewsets.GenericViewSet):
+    mixins.CreateModelMixin,
+    mixins.RetrieveModelMixin,
+    mixins.ListModelMixin,
+    viewsets.GenericViewSet,
+):
 
     serializer_class = serializers.ImportRequestSerializer
     queryset = (
-        models.ImportRequest.objects.all()
-              .select_related()
-              .order_by('-creation_date'))
+        models.ImportRequest.objects.all().select_related().order_by("-creation_date")
+    )
     filter_class = filters.ImportRequestFilter
-    ordering_fields = ('id', 'artist_name', 'creation_date', 'status')
+    ordering_fields = ("id", "artist_name", "creation_date", "status")
 
     def perform_create(self, serializer):
         return serializer.save(user=self.request.user)
@@ -28,5 +28,5 @@ class ImportRequestViewSet(
     def get_serializer_context(self):
         context = super().get_serializer_context()
         if self.request.user.is_authenticated:
-            context['user'] = self.request.user
+            context["user"] = self.request.user
         return context
diff --git a/api/funkwhale_api/subsonic/authentication.py b/api/funkwhale_api/subsonic/authentication.py
index fe9b08dc8a..808e5e5ec3 100644
--- a/api/funkwhale_api/subsonic/authentication.py
+++ b/api/funkwhale_api/subsonic/authentication.py
@@ -10,23 +10,20 @@ from funkwhale_api.users.models import User
 def get_token(salt, password):
     to_hash = password + salt
     h = hashlib.md5()
-    h.update(to_hash.encode('utf-8'))
+    h.update(to_hash.encode("utf-8"))
     return h.hexdigest()
 
 
 def authenticate(username, password):
     try:
-        if password.startswith('enc:'):
-            password = password.replace('enc:', '', 1)
-            password = binascii.unhexlify(password).decode('utf-8')
+        if password.startswith("enc:"):
+            password = password.replace("enc:", "", 1)
+            password = binascii.unhexlify(password).decode("utf-8")
         user = User.objects.get(
-            username=username,
-            is_active=True,
-            subsonic_api_token=password)
-    except (User.DoesNotExist, binascii.Error):
-        raise exceptions.AuthenticationFailed(
-            'Wrong username or password.'
+            username=username, is_active=True, subsonic_api_token=password
         )
+    except (User.DoesNotExist, binascii.Error):
+        raise exceptions.AuthenticationFailed("Wrong username or password.")
 
     return (user, None)
 
@@ -34,18 +31,13 @@ def authenticate(username, password):
 def authenticate_salt(username, salt, token):
     try:
         user = User.objects.get(
-            username=username,
-            is_active=True,
-            subsonic_api_token__isnull=False)
-    except User.DoesNotExist:
-        raise exceptions.AuthenticationFailed(
-            'Wrong username or password.'
+            username=username, is_active=True, subsonic_api_token__isnull=False
         )
+    except User.DoesNotExist:
+        raise exceptions.AuthenticationFailed("Wrong username or password.")
     expected = get_token(salt, user.subsonic_api_token)
     if expected != token:
-        raise exceptions.AuthenticationFailed(
-            'Wrong username or password.'
-        )
+        raise exceptions.AuthenticationFailed("Wrong username or password.")
 
     return (user, None)
 
@@ -53,15 +45,15 @@ def authenticate_salt(username, salt, token):
 class SubsonicAuthentication(authentication.BaseAuthentication):
     def authenticate(self, request):
         data = request.GET or request.POST
-        username = data.get('u')
+        username = data.get("u")
         if not username:
             return None
 
-        p = data.get('p')
-        s = data.get('s')
-        t = data.get('t')
+        p = data.get("p")
+        s = data.get("s")
+        t = data.get("t")
         if not p and (not s or not t):
-            raise exceptions.AuthenticationFailed('Missing credentials')
+            raise exceptions.AuthenticationFailed("Missing credentials")
 
         if p:
             return authenticate(username, p)
diff --git a/api/funkwhale_api/subsonic/dynamic_preferences_registry.py b/api/funkwhale_api/subsonic/dynamic_preferences_registry.py
index 93482702ff..0916b0a3e9 100644
--- a/api/funkwhale_api/subsonic/dynamic_preferences_registry.py
+++ b/api/funkwhale_api/subsonic/dynamic_preferences_registry.py
@@ -3,20 +3,20 @@ from dynamic_preferences.registries import global_preferences_registry
 
 from funkwhale_api.common import preferences
 
-subsonic = types.Section('subsonic')
+subsonic = types.Section("subsonic")
 
 
 @global_preferences_registry.register
 class APIAutenticationRequired(types.BooleanPreference):
     section = subsonic
     show_in_api = True
-    name = 'enabled'
+    name = "enabled"
     default = True
-    verbose_name = 'Enabled Subsonic API'
+    verbose_name = "Enabled Subsonic API"
     help_text = (
-        'Funkwhale supports a subset of the Subsonic API, that makes '
-        'it compatible with existing clients such as DSub for Android '
-        'or Clementine for desktop. However, Subsonic protocol is less '
-        'than ideal in terms of security and you can disable this feature '
-        'completely using this flag.'
+        "Funkwhale supports a subset of the Subsonic API, that makes "
+        "it compatible with existing clients such as DSub for Android "
+        "or Clementine for desktop. However, Subsonic protocol is less "
+        "than ideal in terms of security and you can disable this feature "
+        "completely using this flag."
     )
diff --git a/api/funkwhale_api/subsonic/filters.py b/api/funkwhale_api/subsonic/filters.py
index b7b639fac3..a354e23f11 100644
--- a/api/funkwhale_api/subsonic/filters.py
+++ b/api/funkwhale_api/subsonic/filters.py
@@ -4,18 +4,18 @@ from funkwhale_api.music import models as music_models
 
 
 class AlbumList2FilterSet(filters.FilterSet):
-    type = filters.CharFilter(name='_', method='filter_type')
+    type = filters.CharFilter(name="_", method="filter_type")
 
     class Meta:
         model = music_models.Album
-        fields = ['type']
+        fields = ["type"]
 
     def filter_type(self, queryset, name, value):
         ORDERING = {
-            'random': '?',
-            'newest': '-creation_date',
-            'alphabeticalByArtist': 'artist__name',
-            'alphabeticalByName': 'title',
+            "random": "?",
+            "newest": "-creation_date",
+            "alphabeticalByArtist": "artist__name",
+            "alphabeticalByName": "title",
         }
         if value not in ORDERING:
             return queryset
diff --git a/api/funkwhale_api/subsonic/negotiation.py b/api/funkwhale_api/subsonic/negotiation.py
index 3335fda45b..2b46b17530 100644
--- a/api/funkwhale_api/subsonic/negotiation.py
+++ b/api/funkwhale_api/subsonic/negotiation.py
@@ -5,8 +5,8 @@ from . import renderers
 
 
 MAPPING = {
-    'json': (renderers.SubsonicJSONRenderer(), 'application/json'),
-    'xml': (renderers.SubsonicXMLRenderer(), 'text/xml'),
+    "json": (renderers.SubsonicJSONRenderer(), "application/json"),
+    "xml": (renderers.SubsonicXMLRenderer(), "text/xml"),
 }
 
 
@@ -14,7 +14,7 @@ class SubsonicContentNegociation(negotiation.DefaultContentNegotiation):
     def select_renderer(self, request, renderers, format_suffix=None):
         path = request.path
         data = request.GET or request.POST
-        requested_format = data.get('f', 'xml')
+        requested_format = data.get("f", "xml")
         try:
             return MAPPING[requested_format]
         except KeyError:
diff --git a/api/funkwhale_api/subsonic/renderers.py b/api/funkwhale_api/subsonic/renderers.py
index 3a56645012..fac12d6c19 100644
--- a/api/funkwhale_api/subsonic/renderers.py
+++ b/api/funkwhale_api/subsonic/renderers.py
@@ -8,37 +8,34 @@ class SubsonicJSONRenderer(renderers.JSONRenderer):
         if not data:
             # when stream view is called, we don't have any data
             return super().render(data, accepted_media_type, renderer_context)
-        final = {
-            'subsonic-response': {
-                'status': 'ok',
-                'version': '1.16.0',
-            }
-        }
-        final['subsonic-response'].update(data)
-        if 'error' in final:
+        final = {"subsonic-response": {"status": "ok", "version": "1.16.0"}}
+        final["subsonic-response"].update(data)
+        if "error" in final:
             # an error was returned
-            final['subsonic-response']['status'] = 'failed'
+            final["subsonic-response"]["status"] = "failed"
         return super().render(final, accepted_media_type, renderer_context)
 
 
 class SubsonicXMLRenderer(renderers.JSONRenderer):
-    media_type = 'text/xml'
+    media_type = "text/xml"
 
     def render(self, data, accepted_media_type=None, renderer_context=None):
         if not data:
             # when stream view is called, we don't have any data
             return super().render(data, accepted_media_type, renderer_context)
         final = {
-            'xmlns': 'http://subsonic.org/restapi',
-            'status': 'ok',
-            'version': '1.16.0',
+            "xmlns": "http://subsonic.org/restapi",
+            "status": "ok",
+            "version": "1.16.0",
         }
         final.update(data)
-        if 'error' in final:
+        if "error" in final:
             # an error was returned
-            final['status'] = 'failed'
-        tree = dict_to_xml_tree('subsonic-response', final)
-        return b'<?xml version="1.0" encoding="UTF-8"?>\n' + ET.tostring(tree, encoding='utf-8')
+            final["status"] = "failed"
+        tree = dict_to_xml_tree("subsonic-response", final)
+        return b'<?xml version="1.0" encoding="UTF-8"?>\n' + ET.tostring(
+            tree, encoding="utf-8"
+        )
 
 
 def dict_to_xml_tree(root_tag, d, parent=None):
diff --git a/api/funkwhale_api/subsonic/serializers.py b/api/funkwhale_api/subsonic/serializers.py
index 97cdbcfc69..7735b1a78e 100644
--- a/api/funkwhale_api/subsonic/serializers.py
+++ b/api/funkwhale_api/subsonic/serializers.py
@@ -10,106 +10,100 @@ from funkwhale_api.music import models as music_models
 
 def get_artist_data(artist_values):
     return {
-        'id': artist_values['id'],
-        'name': artist_values['name'],
-        'albumCount': artist_values['_albums_count']
+        "id": artist_values["id"],
+        "name": artist_values["name"],
+        "albumCount": artist_values["_albums_count"],
     }
 
 
 class GetArtistsSerializer(serializers.Serializer):
     def to_representation(self, queryset):
-        payload = {
-            'ignoredArticles': '',
-            'index': []
-        }
+        payload = {"ignoredArticles": "", "index": []}
         queryset = queryset.with_albums_count()
-        queryset = queryset.order_by(functions.Lower('name'))
-        values = queryset.values('id', '_albums_count', 'name')
+        queryset = queryset.order_by(functions.Lower("name"))
+        values = queryset.values("id", "_albums_count", "name")
 
         first_letter_mapping = collections.defaultdict(list)
         for artist in values:
-            first_letter_mapping[artist['name'][0].upper()].append(artist)
+            first_letter_mapping[artist["name"][0].upper()].append(artist)
 
         for letter, artists in sorted(first_letter_mapping.items()):
             letter_data = {
-                'name': letter,
-                'artist': [
-                    get_artist_data(v)
-                    for v in artists
-                ]
+                "name": letter,
+                "artist": [get_artist_data(v) for v in artists],
             }
-            payload['index'].append(letter_data)
+            payload["index"].append(letter_data)
         return payload
 
 
 class GetArtistSerializer(serializers.Serializer):
     def to_representation(self, artist):
-        albums = artist.albums.prefetch_related('tracks__files')
+        albums = artist.albums.prefetch_related("tracks__files")
         payload = {
-            'id': artist.pk,
-            'name': artist.name,
-            'albumCount': len(albums),
-            'album': [],
+            "id": artist.pk,
+            "name": artist.name,
+            "albumCount": len(albums),
+            "album": [],
         }
         for album in albums:
             album_data = {
-                'id': album.id,
-                'artistId': artist.id,
-                'name': album.title,
-                'artist': artist.name,
-                'created': album.creation_date,
-                'songCount': len(album.tracks.all()),
+                "id": album.id,
+                "artistId": artist.id,
+                "name": album.title,
+                "artist": artist.name,
+                "created": album.creation_date,
+                "songCount": len(album.tracks.all()),
             }
             if album.cover:
-                album_data['coverArt'] = 'al-{}'.format(album.id)
+                album_data["coverArt"] = "al-{}".format(album.id)
             if album.release_date:
-                album_data['year'] = album.release_date.year
-            payload['album'].append(album_data)
+                album_data["year"] = album.release_date.year
+            payload["album"].append(album_data)
         return payload
 
 
 def get_track_data(album, track, tf):
     data = {
-        'id': track.pk,
-        'isDir': 'false',
-        'title': track.title,
-        'album': album.title,
-        'artist': album.artist.name,
-        'track': track.position or 1,
-        'contentType': tf.mimetype,
-        'suffix': tf.extension or '',
-        'duration': tf.duration or 0,
-        'created': track.creation_date,
-        'albumId': album.pk,
-        'artistId': album.artist.pk,
-        'type': 'music',
+        "id": track.pk,
+        "isDir": "false",
+        "title": track.title,
+        "album": album.title,
+        "artist": album.artist.name,
+        "track": track.position or 1,
+        "contentType": tf.mimetype,
+        "suffix": tf.extension or "",
+        "duration": tf.duration or 0,
+        "created": track.creation_date,
+        "albumId": album.pk,
+        "artistId": album.artist.pk,
+        "type": "music",
     }
     if track.album.cover:
-        data['coverArt'] = 'al-{}'.format(track.album.id)
+        data["coverArt"] = "al-{}".format(track.album.id)
     if tf.bitrate:
-        data['bitrate'] = int(tf.bitrate/1000)
+        data["bitrate"] = int(tf.bitrate / 1000)
     if tf.size:
-        data['size'] = tf.size
+        data["size"] = tf.size
     if album.release_date:
-        data['year'] = album.release_date.year
+        data["year"] = album.release_date.year
     return data
 
 
 def get_album2_data(album):
     payload = {
-        'id': album.id,
-        'artistId': album.artist.id,
-        'name': album.title,
-        'artist': album.artist.name,
-        'created': album.creation_date,
+        "id": album.id,
+        "artistId": album.artist.id,
+        "name": album.title,
+        "artist": album.artist.name,
+        "created": album.creation_date,
     }
     if album.cover:
-        payload['coverArt'] = 'al-{}'.format(album.id)
+        payload["coverArt"] = "al-{}".format(album.id)
 
     try:
-        payload['songCount'] = album._tracks_count
+        payload["songCount"] = album._tracks_count
     except AttributeError:
-        payload['songCount'] = len(album.tracks.prefetch_related('files'))
+        payload["songCount"] = len(album.tracks.prefetch_related("files"))
     return payload
 
 
@@ -127,24 +121,23 @@ def get_song_list_data(tracks):
 
 class GetAlbumSerializer(serializers.Serializer):
     def to_representation(self, album):
-        tracks = album.tracks.prefetch_related('files').select_related('album')
+        tracks = album.tracks.prefetch_related("files").select_related("album")
         payload = get_album2_data(album)
         if album.release_date:
-            payload['year'] = album.release_date.year
+            payload["year"] = album.release_date.year
 
-        payload['song'] = get_song_list_data(tracks)
+        payload["song"] = get_song_list_data(tracks)
         return payload
 
 
 def get_starred_tracks_data(favorites):
-    by_track_id = {
-        f.track_id: f
-        for f in favorites
-    }
-    tracks = music_models.Track.objects.filter(
-        pk__in=by_track_id.keys()
-    ).select_related('album__artist').prefetch_related('files')
-    tracks = tracks.order_by('-creation_date')
+    by_track_id = {f.track_id: f for f in favorites}
+    tracks = (
+        music_models.Track.objects.filter(pk__in=by_track_id.keys())
+        .select_related("album__artist")
+        .prefetch_related("files")
+    )
+    tracks = tracks.order_by("-creation_date")
     data = []
     for t in tracks:
         try:
@@ -152,54 +145,48 @@ def get_starred_tracks_data(favorites):
         except IndexError:
             continue
         td = get_track_data(t.album, t, tf)
-        td['starred'] = by_track_id[t.pk].creation_date
+        td["starred"] = by_track_id[t.pk].creation_date
         data.append(td)
     return data
 
 
 def get_album_list2_data(albums):
-    return [
-        get_album2_data(a)
-        for a in albums
-    ]
+    return [get_album2_data(a) for a in albums]
 
 
 def get_playlist_data(playlist):
     return {
-        'id': playlist.pk,
-        'name': playlist.name,
-        'owner': playlist.user.username,
-        'public': 'false',
-        'songCount': playlist._tracks_count,
-        'duration': 0,
-        'created': playlist.creation_date,
+        "id": playlist.pk,
+        "name": playlist.name,
+        "owner": playlist.user.username,
+        "public": "false",
+        "songCount": playlist._tracks_count,
+        "duration": 0,
+        "created": playlist.creation_date,
     }
 
 
 def get_playlist_detail_data(playlist):
     data = get_playlist_data(playlist)
-    qs = playlist.playlist_tracks.select_related(
-        'track__album__artist'
-    ).prefetch_related('track__files').order_by('index')
-    data['entry'] = []
+    qs = (
+        playlist.playlist_tracks.select_related("track__album__artist")
+        .prefetch_related("track__files")
+        .order_by("index")
+    )
+    data["entry"] = []
     for plt in qs:
         try:
             tf = [tf for tf in plt.track.files.all()][0]
         except IndexError:
             continue
         td = get_track_data(plt.track.album, plt.track, tf)
-        data['entry'].append(td)
+        data["entry"].append(td)
     return data
 
 
 def get_music_directory_data(artist):
-    tracks = artist.tracks.select_related('album').prefetch_related('files')
-    data = {
-        'id': artist.pk,
-        'parent': 1,
-        'name': artist.name,
-        'child': []
-    }
+    tracks = artist.tracks.select_related("album").prefetch_related("files")
+    data = {"id": artist.pk, "parent": 1, "name": artist.name, "child": []}
     for track in tracks:
         try:
             tf = [tf for tf in track.files.all()][0]
@@ -207,40 +194,39 @@ def get_music_directory_data(artist):
             continue
         album = track.album
         td = {
-            'id': track.pk,
-            'isDir': 'false',
-            'title': track.title,
-            'album': album.title,
-            'artist': artist.name,
-            'track': track.position or 1,
-            'year': track.album.release_date.year if track.album.release_date else 0,
-            'contentType': tf.mimetype,
-            'suffix': tf.extension or '',
-            'duration': tf.duration or 0,
-            'created': track.creation_date,
-            'albumId': album.pk,
-            'artistId': artist.pk,
-            'parent': artist.id,
-            'type': 'music',
+            "id": track.pk,
+            "isDir": "false",
+            "title": track.title,
+            "album": album.title,
+            "artist": artist.name,
+            "track": track.position or 1,
+            "year": track.album.release_date.year if track.album.release_date else 0,
+            "contentType": tf.mimetype,
+            "suffix": tf.extension or "",
+            "duration": tf.duration or 0,
+            "created": track.creation_date,
+            "albumId": album.pk,
+            "artistId": artist.pk,
+            "parent": artist.id,
+            "type": "music",
         }
         if tf.bitrate:
-            td['bitrate'] = int(tf.bitrate/1000)
+            td["bitrate"] = int(tf.bitrate / 1000)
         if tf.size:
-            td['size'] = tf.size
-        data['child'].append(td)
+            td["size"] = tf.size
+        data["child"].append(td)
     return data
 
 
 class ScrobbleSerializer(serializers.Serializer):
     submission = serializers.BooleanField(default=True, required=False)
     id = serializers.PrimaryKeyRelatedField(
-        queryset=music_models.Track.objects.annotate(
-            files_count=Count('files')
-        ).filter(files_count__gt=0)
+        queryset=music_models.Track.objects.annotate(files_count=Count("files")).filter(
+            files_count__gt=0
+        )
     )
 
     def create(self, data):
         return history_models.Listening.objects.create(
-            user=self.context['user'],
-            track=data['id'],
+            user=self.context["user"], track=data["id"]
         )
diff --git a/api/funkwhale_api/subsonic/views.py b/api/funkwhale_api/subsonic/views.py
index cc75b5279e..85d120a086 100644
--- a/api/funkwhale_api/subsonic/views.py
+++ b/api/funkwhale_api/subsonic/views.py
@@ -25,44 +25,55 @@ from . import negotiation
 from . import serializers
 
 
-def find_object(queryset, model_field='pk', field='id', cast=int):
+def find_object(queryset, model_field="pk", field="id", cast=int):
     def decorator(func):
         def inner(self, request, *args, **kwargs):
             data = request.GET or request.POST
             try:
                 raw_value = data[field]
             except KeyError:
-                return response.Response({
-                    'error': {
-                        'code': 10,
-                        'message': "required parameter '{}' not present".format(field)
+                return response.Response(
+                    {
+                        "error": {
+                            "code": 10,
+                            "message": "required parameter '{}' not present".format(
+                                field
+                            ),
+                        }
                     }
-                })
+                )
             try:
                 value = cast(raw_value)
             except (TypeError, ValidationError):
-                return response.Response({
-                    'error': {
-                        'code': 0,
-                        'message': 'For input string "{}"'.format(raw_value)
+                return response.Response(
+                    {
+                        "error": {
+                            "code": 0,
+                            "message": 'For input string "{}"'.format(raw_value),
+                        }
                     }
-                })
+                )
             qs = queryset
-            if hasattr(qs, '__call__'):
+            if hasattr(qs, "__call__"):
                 qs = qs(request)
             try:
                 obj = qs.get(**{model_field: value})
             except qs.model.DoesNotExist:
-                return response.Response({
-                    'error': {
-                        'code': 70,
-                        'message': '{} not found'.format(
-                            qs.model.__class__.__name__)
+                return response.Response(
+                    {
+                        "error": {
+                            "code": 70,
+                            "message": "{} not found".format(
+                                qs.model.__class__.__name__
+                            ),
+                        }
                     }
-                })
-            kwargs['obj'] = obj
+                )
+            kwargs["obj"] = obj
             return func(self, request, *args, **kwargs)
+
         return inner
+
     return decorator
 
 
@@ -72,10 +83,10 @@ class SubsonicViewSet(viewsets.GenericViewSet):
     permissions_classes = [rest_permissions.IsAuthenticated]
 
     def dispatch(self, request, *args, **kwargs):
-        if not preferences.get('subsonic__enabled'):
+        if not preferences.get("subsonic__enabled"):
             r = response.Response({}, status=405)
             r.accepted_renderer = renderers.JSONRenderer()
-            r.accepted_media_type = 'application/json'
+            r.accepted_media_type = "application/json"
             r.renderer_context = {}
             return r
         return super().dispatch(request, *args, **kwargs)
@@ -83,261 +94,186 @@ class SubsonicViewSet(viewsets.GenericViewSet):
     def handle_exception(self, exc):
         # subsonic API sends 200 status code with custom error
         # codes in the payload
-        mapping = {
-            exceptions.AuthenticationFailed: (
-                40, 'Wrong username or password.'
-            )
-        }
-        payload = {
-            'status': 'failed'
-        }
+        mapping = {exceptions.AuthenticationFailed: (40, "Wrong username or password.")}
+        payload = {"status": "failed"}
         if exc.__class__ in mapping:
             code, message = mapping[exc.__class__]
         else:
             return super().handle_exception(exc)
-        payload['error'] = {
-            'code': code,
-            'message': message
-        }
+        payload["error"] = {"code": code, "message": message}
 
         return response.Response(payload, status=200)
 
-    @list_route(
-        methods=['get', 'post'],
-        permission_classes=[])
+    @list_route(methods=["get", "post"], permission_classes=[])
     def ping(self, request, *args, **kwargs):
-        data = {
-            'status': 'ok',
-            'version': '1.16.0'
-        }
+        data = {"status": "ok", "version": "1.16.0"}
         return response.Response(data, status=200)
 
     @list_route(
-        methods=['get', 'post'],
-        url_name='get_license',
+        methods=["get", "post"],
+        url_name="get_license",
         permissions_classes=[],
-        url_path='getLicense')
+        url_path="getLicense",
+    )
     def get_license(self, request, *args, **kwargs):
         now = timezone.now()
         data = {
-            'status': 'ok',
-            'version': '1.16.0',
-            'license': {
-                'valid': 'true',
-                'email': 'valid@valid.license',
-                'licenseExpires': now + datetime.timedelta(days=365)
-            }
+            "status": "ok",
+            "version": "1.16.0",
+            "license": {
+                "valid": "true",
+                "email": "valid@valid.license",
+                "licenseExpires": now + datetime.timedelta(days=365),
+            },
         }
         return response.Response(data, status=200)
 
-    @list_route(
-        methods=['get', 'post'],
-        url_name='get_artists',
-        url_path='getArtists')
+    @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()
         data = serializers.GetArtistsSerializer(artists).data
-        payload = {
-            'artists': data
-        }
+        payload = {"artists": data}
 
         return response.Response(payload, status=200)
 
-    @list_route(
-        methods=['get', 'post'],
-        url_name='get_indexes',
-        url_path='getIndexes')
+    @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()
         data = serializers.GetArtistsSerializer(artists).data
-        payload = {
-            'indexes': data
-        }
+        payload = {"indexes": data}
 
         return response.Response(payload, status=200)
 
-    @list_route(
-        methods=['get', 'post'],
-        url_name='get_artist',
-        url_path='getArtist')
+    @list_route(methods=["get", "post"], url_name="get_artist", url_path="getArtist")
     @find_object(music_models.Artist.objects.all())
     def get_artist(self, request, *args, **kwargs):
-        artist = kwargs.pop('obj')
+        artist = kwargs.pop("obj")
         data = serializers.GetArtistSerializer(artist).data
-        payload = {
-            'artist': data
-        }
+        payload = {"artist": data}
 
         return response.Response(payload, status=200)
 
     @list_route(
-        methods=['get', 'post'],
-        url_name='get_artist_info2',
-        url_path='getArtistInfo2')
+        methods=["get", "post"], url_name="get_artist_info2", url_path="getArtistInfo2"
+    )
     @find_object(music_models.Artist.objects.all())
     def get_artist_info2(self, request, *args, **kwargs):
-        artist = kwargs.pop('obj')
-        payload = {
-            'artist-info2': {}
-        }
+        artist = kwargs.pop("obj")
+        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'))
+    @list_route(methods=["get", "post"], url_name="get_album", url_path="getAlbum")
+    @find_object(music_models.Album.objects.select_related("artist"))
     def get_album(self, request, *args, **kwargs):
-        album = kwargs.pop('obj')
+        album = kwargs.pop("obj")
         data = serializers.GetAlbumSerializer(album).data
-        payload = {
-            'album': data
-        }
+        payload = {"album": data}
         return response.Response(payload, status=200)
 
-    @list_route(
-        methods=['get', 'post'],
-        url_name='stream',
-        url_path='stream')
-    @find_object(
-        music_models.Track.objects.all())
+    @list_route(methods=["get", "post"], url_name="stream", url_path="stream")
+    @find_object(music_models.Track.objects.all())
     def stream(self, request, *args, **kwargs):
-        track = kwargs.pop('obj')
+        track = kwargs.pop("obj")
         queryset = track.files.select_related(
-            'library_track',
-            'track__album__artist',
-            'track__artist',
+            "library_track", "track__album__artist", "track__artist"
         )
         track_file = queryset.first()
         if not track_file:
             return response.Response(status=404)
         return music_views.handle_serve(track_file)
 
-    @list_route(
-        methods=['get', 'post'],
-        url_name='star',
-        url_path='star')
-    @find_object(
-        music_models.Track.objects.all())
+    @list_route(methods=["get", "post"], url_name="star", url_path="star")
+    @find_object(music_models.Track.objects.all())
     def star(self, request, *args, **kwargs):
-        track = kwargs.pop('obj')
+        track = kwargs.pop("obj")
         TrackFavorite.add(user=request.user, track=track)
-        return response.Response({'status': 'ok'})
+        return response.Response({"status": "ok"})
 
-    @list_route(
-        methods=['get', 'post'],
-        url_name='unstar',
-        url_path='unstar')
-    @find_object(
-        music_models.Track.objects.all())
+    @list_route(methods=["get", "post"], url_name="unstar", url_path="unstar")
+    @find_object(music_models.Track.objects.all())
     def unstar(self, request, *args, **kwargs):
-        track = kwargs.pop('obj')
+        track = kwargs.pop("obj")
         request.user.track_favorites.filter(track=track).delete()
-        return response.Response({'status': 'ok'})
+        return response.Response({"status": "ok"})
 
     @list_route(
-        methods=['get', 'post'],
-        url_name='get_starred2',
-        url_path='getStarred2')
+        methods=["get", "post"], url_name="get_starred2", url_path="getStarred2"
+    )
     def get_starred2(self, request, *args, **kwargs):
         favorites = request.user.track_favorites.all()
-        data = {
-            'starred2': {
-                'song': serializers.get_starred_tracks_data(favorites)
-            }
-        }
+        data = {"starred2": {"song": serializers.get_starred_tracks_data(favorites)}}
         return response.Response(data)
 
-    @list_route(
-        methods=['get', 'post'],
-        url_name='get_starred',
-        url_path='getStarred')
+    @list_route(methods=["get", "post"], url_name="get_starred", url_path="getStarred")
     def get_starred(self, request, *args, **kwargs):
         favorites = request.user.track_favorites.all()
-        data = {
-            'starred': {
-                'song': serializers.get_starred_tracks_data(favorites)
-            }
-        }
+        data = {"starred": {"song": serializers.get_starred_tracks_data(favorites)}}
         return response.Response(data)
 
     @list_route(
-        methods=['get', 'post'],
-        url_name='get_album_list2',
-        url_path='getAlbumList2')
+        methods=["get", "post"], url_name="get_album_list2", url_path="getAlbumList2"
+    )
     def get_album_list2(self, request, *args, **kwargs):
         queryset = music_models.Album.objects.with_tracks_count()
         data = request.GET or request.POST
         filterset = filters.AlbumList2FilterSet(data, queryset=queryset)
         queryset = filterset.qs
         try:
-            offset = int(data['offset'])
+            offset = int(data["offset"])
         except (TypeError, KeyError, ValueError):
             offset = 0
 
         try:
-            size = int(data['size'])
+            size = int(data["size"])
         except (TypeError, KeyError, ValueError):
             size = 50
 
         size = min(size, 500)
         queryset = queryset[offset:size]
-        data = {
-            'albumList2': {
-                'album': serializers.get_album_list2_data(queryset)
-            }
-        }
+        data = {"albumList2": {"album": serializers.get_album_list2_data(queryset)}}
         return response.Response(data)
 
-    @list_route(
-        methods=['get', 'post'],
-        url_name='search3',
-        url_path='search3')
+    @list_route(methods=["get", "post"], url_name="search3", url_path="search3")
     def search3(self, request, *args, **kwargs):
         data = request.GET or request.POST
-        query = str(data.get('query', '')).replace('*', '')
+        query = str(data.get("query", "")).replace("*", "")
         conf = [
             {
-                'subsonic': 'artist',
-                'search_fields': ['name'],
-                'queryset': (
-                    music_models.Artist.objects
-                                       .with_albums_count()
-                                       .values('id', '_albums_count', 'name')
+                "subsonic": "artist",
+                "search_fields": ["name"],
+                "queryset": (
+                    music_models.Artist.objects.with_albums_count().values(
+                        "id", "_albums_count", "name"
+                    )
                 ),
-                'serializer': lambda qs: [
-                    serializers.get_artist_data(a) for a in qs
-                ]
+                "serializer": lambda qs: [serializers.get_artist_data(a) for a in qs],
             },
             {
-                'subsonic': 'album',
-                'search_fields': ['title'],
-                'queryset': (
-                    music_models.Album.objects
-                                .with_tracks_count()
-                                .select_related('artist')
+                "subsonic": "album",
+                "search_fields": ["title"],
+                "queryset": (
+                    music_models.Album.objects.with_tracks_count().select_related(
+                        "artist"
+                    )
                 ),
-                'serializer': serializers.get_album_list2_data,
+                "serializer": serializers.get_album_list2_data,
             },
             {
-                'subsonic': 'song',
-                'search_fields': ['title'],
-                'queryset': (
-                    music_models.Track.objects
-                                .prefetch_related('files')
-                                .select_related('album__artist')
+                "subsonic": "song",
+                "search_fields": ["title"],
+                "queryset": (
+                    music_models.Track.objects.prefetch_related("files").select_related(
+                        "album__artist"
+                    )
                 ),
-                'serializer': serializers.get_song_list_data,
+                "serializer": serializers.get_song_list_data,
             },
         ]
-        payload = {
-            'searchResult3': {}
-        }
+        payload = {"searchResult3": {}}
         for c in conf:
-            offsetKey = '{}Offset'.format(c['subsonic'])
-            countKey = '{}Count'.format(c['subsonic'])
+            offsetKey = "{}Offset".format(c["subsonic"])
+            countKey = "{}Count".format(c["subsonic"])
             try:
                 offset = int(data[offsetKey])
             except (TypeError, KeyError, ValueError):
@@ -349,60 +285,49 @@ class SubsonicViewSet(viewsets.GenericViewSet):
                 size = 20
 
             size = min(size, 100)
-            queryset = c['queryset']
+            queryset = c["queryset"]
             if query:
-                queryset = c['queryset'].filter(
-                    utils.get_query(query, c['search_fields'])
+                queryset = c["queryset"].filter(
+                    utils.get_query(query, c["search_fields"])
                 )
             queryset = queryset[offset:size]
-            payload['searchResult3'][c['subsonic']] = c['serializer'](queryset)
+            payload["searchResult3"][c["subsonic"]] = c["serializer"](queryset)
         return response.Response(payload)
 
     @list_route(
-        methods=['get', 'post'],
-        url_name='get_playlists',
-        url_path='getPlaylists')
+        methods=["get", "post"], url_name="get_playlists", url_path="getPlaylists"
+    )
     def get_playlists(self, request, *args, **kwargs):
-        playlists = request.user.playlists.with_tracks_count().select_related(
-            'user'
-        )
+        playlists = request.user.playlists.with_tracks_count().select_related("user")
         data = {
-            'playlists': {
-                'playlist': [
-                    serializers.get_playlist_data(p) for p in playlists]
+            "playlists": {
+                "playlist": [serializers.get_playlist_data(p) for p in playlists]
             }
         }
         return response.Response(data)
 
     @list_route(
-        methods=['get', 'post'],
-        url_name='get_playlist',
-        url_path='getPlaylist')
-    @find_object(
-        playlists_models.Playlist.objects.with_tracks_count())
+        methods=["get", "post"], url_name="get_playlist", url_path="getPlaylist"
+    )
+    @find_object(playlists_models.Playlist.objects.with_tracks_count())
     def get_playlist(self, request, *args, **kwargs):
-        playlist = kwargs.pop('obj')
-        data = {
-            'playlist': serializers.get_playlist_detail_data(playlist)
-        }
+        playlist = kwargs.pop("obj")
+        data = {"playlist": serializers.get_playlist_detail_data(playlist)}
         return response.Response(data)
 
     @list_route(
-        methods=['get', 'post'],
-        url_name='update_playlist',
-        url_path='updatePlaylist')
-    @find_object(
-        lambda request: request.user.playlists.all(),
-        field='playlistId')
+        methods=["get", "post"], url_name="update_playlist", url_path="updatePlaylist"
+    )
+    @find_object(lambda request: request.user.playlists.all(), field="playlistId")
     def update_playlist(self, request, *args, **kwargs):
-        playlist = kwargs.pop('obj')
+        playlist = kwargs.pop("obj")
         data = request.GET or request.POST
-        new_name = data.get('name', '')
+        new_name = data.get("name", "")
         if new_name:
             playlist.name = new_name
-            playlist.save(update_fields=['name', 'modification_date'])
+            playlist.save(update_fields=["name", "modification_date"])
         try:
-            to_remove = int(data['songIndexToRemove'])
+            to_remove = int(data["songIndexToRemove"])
             plt = playlist.playlist_tracks.get(index=to_remove)
         except (TypeError, ValueError, KeyError):
             pass
@@ -412,7 +337,7 @@ class SubsonicViewSet(viewsets.GenericViewSet):
             plt.delete(update_indexes=True)
 
         ids = []
-        for i in data.getlist('songIdToAdd'):
+        for i in data.getlist("songIdToAdd"):
             try:
                 ids.append(int(i))
             except (TypeError, ValueError):
@@ -429,45 +354,38 @@ class SubsonicViewSet(viewsets.GenericViewSet):
             if sorted_tracks:
                 playlist.insert_many(sorted_tracks)
 
-        data = {
-            'status': 'ok'
-        }
+        data = {"status": "ok"}
         return response.Response(data)
 
     @list_route(
-        methods=['get', 'post'],
-        url_name='delete_playlist',
-        url_path='deletePlaylist')
-    @find_object(
-        lambda request: request.user.playlists.all())
+        methods=["get", "post"], url_name="delete_playlist", url_path="deletePlaylist"
+    )
+    @find_object(lambda request: request.user.playlists.all())
     def delete_playlist(self, request, *args, **kwargs):
-        playlist = kwargs.pop('obj')
+        playlist = kwargs.pop("obj")
         playlist.delete()
-        data = {
-            'status': 'ok'
-        }
+        data = {"status": "ok"}
         return response.Response(data)
 
     @list_route(
-        methods=['get', 'post'],
-        url_name='create_playlist',
-        url_path='createPlaylist')
+        methods=["get", "post"], url_name="create_playlist", url_path="createPlaylist"
+    )
     def create_playlist(self, request, *args, **kwargs):
         data = request.GET or request.POST
-        name = data.get('name', '')
+        name = data.get("name", "")
         if not name:
-            return response.Response({
-                'error': {
-                    'code': 10,
-                    'message': 'Playlist ID or name must be specified.'
+            return response.Response(
+                {
+                    "error": {
+                        "code": 10,
+                        "message": "Playlist ID or name must be specified.",
+                    }
                 }
-            })
+            )
 
-        playlist = request.user.playlists.create(
-            name=name
-        )
+        playlist = request.user.playlists.create(name=name)
         ids = []
-        for i in data.getlist('songId'):
+        for i in data.getlist("songId"):
             try:
                 ids.append(int(i))
             except (TypeError, ValueError):
@@ -484,92 +402,67 @@ class SubsonicViewSet(viewsets.GenericViewSet):
                     pass
             if sorted_tracks:
                 playlist.insert_many(sorted_tracks)
-        playlist = request.user.playlists.with_tracks_count().get(
-            pk=playlist.pk)
-        data = {
-            'playlist': serializers.get_playlist_detail_data(playlist)
-        }
+        playlist = request.user.playlists.with_tracks_count().get(pk=playlist.pk)
+        data = {"playlist": serializers.get_playlist_detail_data(playlist)}
         return response.Response(data)
 
     @list_route(
-        methods=['get', 'post'],
-        url_name='get_music_folders',
-        url_path='getMusicFolders')
+        methods=["get", "post"],
+        url_name="get_music_folders",
+        url_path="getMusicFolders",
+    )
     def get_music_folders(self, request, *args, **kwargs):
-        data = {
-            'musicFolders': {
-                'musicFolder': [{
-                    'id': 1,
-                    'name': 'Music'
-                }]
-            }
-        }
+        data = {"musicFolders": {"musicFolder": [{"id": 1, "name": "Music"}]}}
         return response.Response(data)
 
     @list_route(
-        methods=['get', 'post'],
-        url_name='get_cover_art',
-        url_path='getCoverArt')
+        methods=["get", "post"], url_name="get_cover_art", url_path="getCoverArt"
+    )
     def get_cover_art(self, request, *args, **kwargs):
         data = request.GET or request.POST
-        id = data.get('id', '')
+        id = data.get("id", "")
         if not id:
-            return response.Response({
-                'error': {
-                    'code': 10,
-                    'message': 'cover art ID must be specified.'
-                }
-            })
+            return response.Response(
+                {"error": {"code": 10, "message": "cover art ID must be specified."}}
+            )
 
-        if id.startswith('al-'):
+        if id.startswith("al-"):
             try:
-                album_id = int(id.replace('al-', ''))
-                album = music_models.Album.objects.exclude(
-                    cover__isnull=True
-                ).exclude(cover='').get(pk=album_id)
+                album_id = int(id.replace("al-", ""))
+                album = (
+                    music_models.Album.objects.exclude(cover__isnull=True)
+                    .exclude(cover="")
+                    .get(pk=album_id)
+                )
             except (TypeError, ValueError, music_models.Album.DoesNotExist):
-                return response.Response({
-                    'error': {
-                        'code': 70,
-                        'message': 'cover art not found.'
-                    }
-                })
+                return response.Response(
+                    {"error": {"code": 70, "message": "cover art not found."}}
+                )
             cover = album.cover
         else:
-            return response.Response({
-                'error': {
-                    'code': 70,
-                    'message': 'cover art not found.'
-                }
-            })
+            return response.Response(
+                {"error": {"code": 70, "message": "cover art not found."}}
+            )
 
-        mapping = {
-            'nginx': 'X-Accel-Redirect',
-            'apache2': 'X-Sendfile',
-        }
+        mapping = {"nginx": "X-Accel-Redirect", "apache2": "X-Sendfile"}
         path = music_views.get_file_path(cover)
         file_header = mapping[settings.REVERSE_PROXY_TYPE]
         # let the proxy set the content-type
-        r = response.Response({}, content_type='')
+        r = response.Response({}, content_type="")
         r[file_header] = path
         return r
 
-    @list_route(
-        methods=['get', 'post'],
-        url_name='scrobble',
-        url_path='scrobble')
+    @list_route(methods=["get", "post"], url_name="scrobble", url_path="scrobble")
     def scrobble(self, request, *args, **kwargs):
         data = request.GET or request.POST
         serializer = serializers.ScrobbleSerializer(
-            data=data, context={'user': request.user})
+            data=data, context={"user": request.user}
+        )
         if not serializer.is_valid():
-            return response.Response({
-                'error': {
-                    'code': 0,
-                    'message': 'Invalid payload'
-                }
-            })
-        if serializer.validated_data['submission']:
+            return response.Response(
+                {"error": {"code": 0, "message": "Invalid payload"}}
+            )
+        if serializer.validated_data["submission"]:
             l = serializer.save()
             record.send(l)
         return response.Response({})
diff --git a/api/funkwhale_api/taskapp/celery.py b/api/funkwhale_api/taskapp/celery.py
index 60b09bece8..80fb1a60e7 100644
--- a/api/funkwhale_api/taskapp/celery.py
+++ b/api/funkwhale_api/taskapp/celery.py
@@ -10,20 +10,22 @@ from django.conf import settings
 
 if not settings.configured:
     # set the default Django settings module for the 'celery' program.
-    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")  # pragma: no cover
+    os.environ.setdefault(
+        "DJANGO_SETTINGS_MODULE", "config.settings.local"
+    )  # pragma: no cover
 
 
-app = Celery('funkwhale_api')
+app = Celery("funkwhale_api")
 
 
 class CeleryConfig(AppConfig):
-    name = 'funkwhale_api.taskapp'
-    verbose_name = 'Celery Config'
+    name = "funkwhale_api.taskapp"
+    verbose_name = "Celery Config"
 
     def ready(self):
         # Using a string here means the worker will not have to
         # pickle the object when using Windows.
-        app.config_from_object('django.conf:settings', namespace='CELERY')
+        app.config_from_object("django.conf:settings", namespace="CELERY")
         app.autodiscover_tasks(lambda: settings.INSTALLED_APPS, force=True)
 
 
@@ -31,7 +33,7 @@ def require_instance(model_or_qs, parameter_name, id_kwarg_name=None):
     def decorator(function):
         @functools.wraps(function)
         def inner(*args, **kwargs):
-            kw = id_kwarg_name or '_'.join([parameter_name, 'id'])
+            kw = id_kwarg_name or "_".join([parameter_name, "id"])
             pk = kwargs.pop(kw)
             try:
                 instance = model_or_qs.get(pk=pk)
@@ -39,5 +41,7 @@ def require_instance(model_or_qs, parameter_name, id_kwarg_name=None):
                 instance = model_or_qs.objects.get(pk=pk)
             kwargs[parameter_name] = instance
             return function(*args, **kwargs)
+
         return inner
+
     return decorator
diff --git a/api/funkwhale_api/users/adapters.py b/api/funkwhale_api/users/adapters.py
index 7bd341d14e..6916975703 100644
--- a/api/funkwhale_api/users/adapters.py
+++ b/api/funkwhale_api/users/adapters.py
@@ -5,11 +5,10 @@ from dynamic_preferences.registries import global_preferences_registry
 
 
 class FunkwhaleAccountAdapter(DefaultAccountAdapter):
-
     def is_open_for_signup(self, request):
         manager = global_preferences_registry.manager()
-        return manager['users__registration_enabled']
+        return manager["users__registration_enabled"]
 
     def send_mail(self, template_prefix, email, context):
-        context['funkwhale_url'] = settings.FUNKWHALE_URL
+        context["funkwhale_url"] = settings.FUNKWHALE_URL
         return super().send_mail(template_prefix, email, context)
diff --git a/api/funkwhale_api/users/admin.py b/api/funkwhale_api/users/admin.py
index cb74abf0e7..5c694ab0ee 100644
--- a/api/funkwhale_api/users/admin.py
+++ b/api/funkwhale_api/users/admin.py
@@ -17,9 +17,9 @@ class MyUserChangeForm(UserChangeForm):
 
 class MyUserCreationForm(UserCreationForm):
 
-    error_message = UserCreationForm.error_messages.update({
-        'duplicate_username': 'This username has already been taken.'
-    })
+    error_message = UserCreationForm.error_messages.update(
+        {"duplicate_username": "This username has already been taken."}
+    )
 
     class Meta(UserCreationForm.Meta):
         model = User
@@ -30,7 +30,7 @@ class MyUserCreationForm(UserCreationForm):
             User.objects.get(username=username)
         except User.DoesNotExist:
             return username
-        raise forms.ValidationError(self.error_messages['duplicate_username'])
+        raise forms.ValidationError(self.error_messages["duplicate_username"])
 
 
 @admin.register(User)
@@ -38,38 +38,39 @@ class UserAdmin(AuthUserAdmin):
     form = MyUserChangeForm
     add_form = MyUserCreationForm
     list_display = [
-        'username',
-        'email',
-        'date_joined',
-        'last_login',
-        'is_staff',
-        'is_superuser',
+        "username",
+        "email",
+        "date_joined",
+        "last_login",
+        "is_staff",
+        "is_superuser",
     ]
     list_filter = [
-        'is_superuser',
-        'is_staff',
-        'privacy_level',
-        'permission_settings',
-        'permission_library',
-        'permission_federation',
+        "is_superuser",
+        "is_staff",
+        "privacy_level",
+        "permission_settings",
+        "permission_library",
+        "permission_federation",
     ]
 
     fieldsets = (
-        (None, {'fields': ('username', 'password', 'privacy_level')}),
-        (_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
-        (_('Permissions'), {
-            'fields': (
-                'is_active',
-                'is_staff',
-                'is_superuser',
-                'permission_upload',
-                'permission_library',
-                'permission_settings',
-                'permission_federation')}),
-        (_('Important dates'), {'fields': ('last_login', 'date_joined')}),
-        (_('Useless fields'), {
-            'fields': (
-                'user_permissions',
-                'groups',
-            )})
-        )
+        (None, {"fields": ("username", "password", "privacy_level")}),
+        (_("Personal info"), {"fields": ("first_name", "last_name", "email")}),
+        (
+            _("Permissions"),
+            {
+                "fields": (
+                    "is_active",
+                    "is_staff",
+                    "is_superuser",
+                    "permission_upload",
+                    "permission_library",
+                    "permission_settings",
+                    "permission_federation",
+                )
+            },
+        ),
+        (_("Important dates"), {"fields": ("last_login", "date_joined")}),
+        (_("Useless fields"), {"fields": ("user_permissions", "groups")}),
+    )
diff --git a/api/funkwhale_api/users/api_urls.py b/api/funkwhale_api/users/api_urls.py
index 8aba7f1a8b..dacb091f1d 100644
--- a/api/funkwhale_api/users/api_urls.py
+++ b/api/funkwhale_api/users/api_urls.py
@@ -2,6 +2,6 @@ from rest_framework import routers
 from . import views
 
 router = routers.SimpleRouter()
-router.register(r'users', views.UserViewSet, 'users')
+router.register(r"users", views.UserViewSet, "users")
 
 urlpatterns = router.urls
diff --git a/api/funkwhale_api/users/dynamic_preferences_registry.py b/api/funkwhale_api/users/dynamic_preferences_registry.py
index 7108360b9a..08f5730a81 100644
--- a/api/funkwhale_api/users/dynamic_preferences_registry.py
+++ b/api/funkwhale_api/users/dynamic_preferences_registry.py
@@ -5,36 +5,26 @@ from funkwhale_api.common import preferences as common_preferences
 
 from . import models
 
-users = types.Section('users')
+users = types.Section("users")
 
 
 @global_preferences_registry.register
 class RegistrationEnabled(types.BooleanPreference):
     show_in_api = True
     section = users
-    name = 'registration_enabled'
+    name = "registration_enabled"
     default = False
-    verbose_name = 'Open registrations to new users'
-    help_text = (
-        'When enabled, new users will be able to register on this instance.'
-    )
+    verbose_name = "Open registrations to new users"
+    help_text = "When enabled, new users will be able to register on this instance."
 
 
 @global_preferences_registry.register
 class DefaultPermissions(common_preferences.StringListPreference):
     show_in_api = True
     section = users
-    name = 'default_permissions'
+    name = "default_permissions"
     default = []
-    verbose_name = 'Default permissions'
-    help_text = (
-        'A list of default preferences to give to all registered users.'
-    )
-    choices = [
-        (k, c['label'])
-        for k, c in models.PERMISSIONS_CONFIGURATION.items()
-    ]
-    field_kwargs = {
-        'choices': choices,
-        'required': False,
-    }
+    verbose_name = "Default permissions"
+    help_text = "A list of default preferences to give to all registered users."
+    choices = [(k, c["label"]) for k, c in models.PERMISSIONS_CONFIGURATION.items()]
+    field_kwargs = {"choices": choices, "required": False}
diff --git a/api/funkwhale_api/users/factories.py b/api/funkwhale_api/users/factories.py
index cd28f44073..949bfdfe40 100644
--- a/api/funkwhale_api/users/factories.py
+++ b/api/funkwhale_api/users/factories.py
@@ -6,10 +6,10 @@ from django.contrib.auth.models import Permission
 
 @registry.register
 class GroupFactory(factory.django.DjangoModelFactory):
-    name = factory.Sequence(lambda n: 'group-{0}'.format(n))
+    name = factory.Sequence(lambda n: "group-{0}".format(n))
 
     class Meta:
-        model = 'auth.Group'
+        model = "auth.Group"
 
     @factory.post_generation
     def perms(self, create, extracted, **kwargs):
@@ -20,8 +20,7 @@ class GroupFactory(factory.django.DjangoModelFactory):
         if extracted:
             perms = [
                 Permission.objects.get(
-                    content_type__app_label=p.split('.')[0],
-                    codename=p.split('.')[1],
+                    content_type__app_label=p.split(".")[0], codename=p.split(".")[1]
                 )
                 for p in extracted
             ]
@@ -31,15 +30,15 @@ class GroupFactory(factory.django.DjangoModelFactory):
 
 @registry.register
 class UserFactory(factory.django.DjangoModelFactory):
-    username = factory.Sequence(lambda n: 'user-{0}'.format(n))
-    email = factory.Sequence(lambda n: 'user-{0}@example.com'.format(n))
-    password = factory.PostGenerationMethodCall('set_password', 'test')
+    username = factory.Sequence(lambda n: "user-{0}".format(n))
+    email = factory.Sequence(lambda n: "user-{0}@example.com".format(n))
+    password = factory.PostGenerationMethodCall("set_password", "test")
     subsonic_api_token = None
-    groups = ManyToManyFromList('groups')
+    groups = ManyToManyFromList("groups")
 
     class Meta:
-        model = 'users.User'
-        django_get_or_create = ('username', )
+        model = "users.User"
+        django_get_or_create = ("username",)
 
     @factory.post_generation
     def perms(self, create, extracted, **kwargs):
@@ -50,8 +49,7 @@ class UserFactory(factory.django.DjangoModelFactory):
         if extracted:
             perms = [
                 Permission.objects.get(
-                    content_type__app_label=p.split('.')[0],
-                    codename=p.split('.')[1],
+                    content_type__app_label=p.split(".")[0], codename=p.split(".")[1]
                 )
                 for p in extracted
             ]
@@ -59,7 +57,7 @@ class UserFactory(factory.django.DjangoModelFactory):
             self.user_permissions.add(*perms)
 
 
-@registry.register(name='users.SuperUser')
+@registry.register(name="users.SuperUser")
 class SuperUserFactory(UserFactory):
     is_staff = True
     is_superuser = True
diff --git a/api/funkwhale_api/users/migrations/0001_initial.py b/api/funkwhale_api/users/migrations/0001_initial.py
index ef9240c915..cc8307c882 100644
--- a/api/funkwhale_api/users/migrations/0001_initial.py
+++ b/api/funkwhale_api/users/migrations/0001_initial.py
@@ -9,36 +9,129 @@ import django.core.validators
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('auth', '0006_require_contenttypes_0002'),
-    ]
+    dependencies = [("auth", "0006_require_contenttypes_0002")]
 
     operations = [
         migrations.CreateModel(
-            name='User',
+            name="User",
             fields=[
-                ('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)),
-                ('password', models.CharField(max_length=128, verbose_name='password')),
-                ('last_login', models.DateTimeField(null=True, verbose_name='last login', blank=True)),
-                ('is_superuser', models.BooleanField(help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status', default=False)),
-                ('username', models.CharField(max_length=30, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.', 'invalid')], verbose_name='username', error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True)),
-                ('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)),
-                ('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)),
-                ('email', models.EmailField(max_length=254, verbose_name='email address', blank=True)),
-                ('is_staff', models.BooleanField(help_text='Designates whether the user can log into this admin site.', verbose_name='staff status', default=False)),
-                ('is_active', models.BooleanField(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active', default=True)),
-                ('date_joined', models.DateTimeField(verbose_name='date joined', default=django.utils.timezone.now)),
-                ('groups', models.ManyToManyField(related_name='user_set', blank=True, verbose_name='groups', to='auth.Group', help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_query_name='user')),
-                ('user_permissions', models.ManyToManyField(related_name='user_set', blank=True, verbose_name='user permissions', to='auth.Permission', help_text='Specific permissions for this user.', related_query_name='user')),
-                ('name', models.CharField(max_length=255, verbose_name='Name of User', blank=True)),
+                (
+                    "id",
+                    models.AutoField(
+                        primary_key=True,
+                        verbose_name="ID",
+                        serialize=False,
+                        auto_created=True,
+                    ),
+                ),
+                ("password", models.CharField(max_length=128, verbose_name="password")),
+                (
+                    "last_login",
+                    models.DateTimeField(
+                        null=True, verbose_name="last login", blank=True
+                    ),
+                ),
+                (
+                    "is_superuser",
+                    models.BooleanField(
+                        help_text="Designates that this user has all permissions without explicitly assigning them.",
+                        verbose_name="superuser status",
+                        default=False,
+                    ),
+                ),
+                (
+                    "username",
+                    models.CharField(
+                        max_length=30,
+                        validators=[
+                            django.core.validators.RegexValidator(
+                                "^[\\w.@+-]+$",
+                                "Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.",
+                                "invalid",
+                            )
+                        ],
+                        verbose_name="username",
+                        error_messages={
+                            "unique": "A user with that username already exists."
+                        },
+                        help_text="Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.",
+                        unique=True,
+                    ),
+                ),
+                (
+                    "first_name",
+                    models.CharField(
+                        max_length=30, verbose_name="first name", blank=True
+                    ),
+                ),
+                (
+                    "last_name",
+                    models.CharField(
+                        max_length=30, verbose_name="last name", blank=True
+                    ),
+                ),
+                (
+                    "email",
+                    models.EmailField(
+                        max_length=254, verbose_name="email address", blank=True
+                    ),
+                ),
+                (
+                    "is_staff",
+                    models.BooleanField(
+                        help_text="Designates whether the user can log into this admin site.",
+                        verbose_name="staff status",
+                        default=False,
+                    ),
+                ),
+                (
+                    "is_active",
+                    models.BooleanField(
+                        help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
+                        verbose_name="active",
+                        default=True,
+                    ),
+                ),
+                (
+                    "date_joined",
+                    models.DateTimeField(
+                        verbose_name="date joined", default=django.utils.timezone.now
+                    ),
+                ),
+                (
+                    "groups",
+                    models.ManyToManyField(
+                        related_name="user_set",
+                        blank=True,
+                        verbose_name="groups",
+                        to="auth.Group",
+                        help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
+                        related_query_name="user",
+                    ),
+                ),
+                (
+                    "user_permissions",
+                    models.ManyToManyField(
+                        related_name="user_set",
+                        blank=True,
+                        verbose_name="user permissions",
+                        to="auth.Permission",
+                        help_text="Specific permissions for this user.",
+                        related_query_name="user",
+                    ),
+                ),
+                (
+                    "name",
+                    models.CharField(
+                        max_length=255, verbose_name="Name of User", blank=True
+                    ),
+                ),
             ],
             options={
-                'verbose_name': 'user',
-                'abstract': False,
-                'verbose_name_plural': 'users',
+                "verbose_name": "user",
+                "abstract": False,
+                "verbose_name_plural": "users",
             },
-            managers=[
-                ('objects', django.contrib.auth.models.UserManager()),
-            ],
-        ),
+            managers=[("objects", django.contrib.auth.models.UserManager())],
+        )
     ]
diff --git a/api/funkwhale_api/users/migrations/0002_auto_20171214_2205.py b/api/funkwhale_api/users/migrations/0002_auto_20171214_2205.py
index 4bbbaa62bc..75fc22035a 100644
--- a/api/funkwhale_api/users/migrations/0002_auto_20171214_2205.py
+++ b/api/funkwhale_api/users/migrations/0002_auto_20171214_2205.py
@@ -9,20 +9,23 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('users', '0001_initial'),
-    ]
+    dependencies = [("users", "0001_initial")]
 
     operations = [
         migrations.AlterModelManagers(
-            name='user',
-            managers=[
-                ('objects', django.contrib.auth.models.UserManager()),
-            ],
+            name="user",
+            managers=[("objects", django.contrib.auth.models.UserManager())],
         ),
         migrations.AlterField(
-            model_name='user',
-            name='username',
-            field=models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username'),
+            model_name="user",
+            name="username",
+            field=models.CharField(
+                error_messages={"unique": "A user with that username already exists."},
+                help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
+                max_length=150,
+                unique=True,
+                validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
+                verbose_name="username",
+            ),
         ),
     ]
diff --git a/api/funkwhale_api/users/migrations/0003_auto_20171226_1357.py b/api/funkwhale_api/users/migrations/0003_auto_20171226_1357.py
index fd75795d3f..62c038b7a8 100644
--- a/api/funkwhale_api/users/migrations/0003_auto_20171226_1357.py
+++ b/api/funkwhale_api/users/migrations/0003_auto_20171226_1357.py
@@ -6,19 +6,19 @@ import uuid
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('users', '0002_auto_20171214_2205'),
-    ]
+    dependencies = [("users", "0002_auto_20171214_2205")]
 
     operations = [
         migrations.AddField(
-            model_name='user',
-            name='secret_key',
+            model_name="user",
+            name="secret_key",
             field=models.UUIDField(default=uuid.uuid4, null=True),
         ),
         migrations.AlterField(
-            model_name='user',
-            name='last_name',
-            field=models.CharField(blank=True, max_length=150, verbose_name='last name'),
+            model_name="user",
+            name="last_name",
+            field=models.CharField(
+                blank=True, max_length=150, verbose_name="last name"
+            ),
         ),
     ]
diff --git a/api/funkwhale_api/users/migrations/0004_user_privacy_level.py b/api/funkwhale_api/users/migrations/0004_user_privacy_level.py
index 81891eb0f0..86b2c75812 100644
--- a/api/funkwhale_api/users/migrations/0004_user_privacy_level.py
+++ b/api/funkwhale_api/users/migrations/0004_user_privacy_level.py
@@ -5,14 +5,21 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('users', '0003_auto_20171226_1357'),
-    ]
+    dependencies = [("users", "0003_auto_20171226_1357")]
 
     operations = [
         migrations.AddField(
-            model_name='user',
-            name='privacy_level',
-            field=models.CharField(choices=[('me', 'Only me'), ('followers', 'Me and my followers'), ('instance', 'Everyone on my instance, and my followers'), ('everyone', 'Everyone, including people on other instances')], default='instance', max_length=30),
-        ),
+            model_name="user",
+            name="privacy_level",
+            field=models.CharField(
+                choices=[
+                    ("me", "Only me"),
+                    ("followers", "Me and my followers"),
+                    ("instance", "Everyone on my instance, and my followers"),
+                    ("everyone", "Everyone, including people on other instances"),
+                ],
+                default="instance",
+                max_length=30,
+            ),
+        )
     ]
diff --git a/api/funkwhale_api/users/migrations/0005_user_subsonic_api_token.py b/api/funkwhale_api/users/migrations/0005_user_subsonic_api_token.py
index 689b3ef779..65a1f1935f 100644
--- a/api/funkwhale_api/users/migrations/0005_user_subsonic_api_token.py
+++ b/api/funkwhale_api/users/migrations/0005_user_subsonic_api_token.py
@@ -5,14 +5,12 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('users', '0004_user_privacy_level'),
-    ]
+    dependencies = [("users", "0004_user_privacy_level")]
 
     operations = [
         migrations.AddField(
-            model_name='user',
-            name='subsonic_api_token',
+            model_name="user",
+            name="subsonic_api_token",
             field=models.CharField(blank=True, max_length=255, null=True),
-        ),
+        )
     ]
diff --git a/api/funkwhale_api/users/migrations/0006_auto_20180517_2324.py b/api/funkwhale_api/users/migrations/0006_auto_20180517_2324.py
index 7c9ab0fadc..d5f6d911b8 100644
--- a/api/funkwhale_api/users/migrations/0006_auto_20180517_2324.py
+++ b/api/funkwhale_api/users/migrations/0006_auto_20180517_2324.py
@@ -5,24 +5,22 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('users', '0005_user_subsonic_api_token'),
-    ]
+    dependencies = [("users", "0005_user_subsonic_api_token")]
 
     operations = [
         migrations.AddField(
-            model_name='user',
-            name='permission_federation',
+            model_name="user",
+            name="permission_federation",
             field=models.BooleanField(default=False),
         ),
         migrations.AddField(
-            model_name='user',
-            name='permission_library',
+            model_name="user",
+            name="permission_library",
             field=models.BooleanField(default=False),
         ),
         migrations.AddField(
-            model_name='user',
-            name='permission_settings',
+            model_name="user",
+            name="permission_settings",
             field=models.BooleanField(default=False),
         ),
     ]
diff --git a/api/funkwhale_api/users/migrations/0007_auto_20180524_2009.py b/api/funkwhale_api/users/migrations/0007_auto_20180524_2009.py
index e3d582c53d..218aa7e481 100644
--- a/api/funkwhale_api/users/migrations/0007_auto_20180524_2009.py
+++ b/api/funkwhale_api/users/migrations/0007_auto_20180524_2009.py
@@ -5,29 +5,37 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
 
-    dependencies = [
-        ('users', '0006_auto_20180517_2324'),
-    ]
+    dependencies = [("users", "0006_auto_20180517_2324")]
 
     operations = [
         migrations.AddField(
-            model_name='user',
-            name='permission_upload',
-            field=models.BooleanField(default=False, verbose_name='Upload new content to the library'),
+            model_name="user",
+            name="permission_upload",
+            field=models.BooleanField(
+                default=False, verbose_name="Upload new content to the library"
+            ),
         ),
         migrations.AlterField(
-            model_name='user',
-            name='permission_federation',
-            field=models.BooleanField(default=False, help_text='Follow other instances, accept/deny library follow requests...', verbose_name='Manage library federation'),
+            model_name="user",
+            name="permission_federation",
+            field=models.BooleanField(
+                default=False,
+                help_text="Follow other instances, accept/deny library follow requests...",
+                verbose_name="Manage library federation",
+            ),
         ),
         migrations.AlterField(
-            model_name='user',
-            name='permission_library',
-            field=models.BooleanField(default=False, help_text='Manage library', verbose_name='Manage library'),
+            model_name="user",
+            name="permission_library",
+            field=models.BooleanField(
+                default=False, help_text="Manage library", verbose_name="Manage library"
+            ),
         ),
         migrations.AlterField(
-            model_name='user',
-            name='permission_settings',
-            field=models.BooleanField(default=False, verbose_name='Manage instance-level settings'),
+            model_name="user",
+            name="permission_settings",
+            field=models.BooleanField(
+                default=False, verbose_name="Manage instance-level settings"
+            ),
         ),
     ]
diff --git a/api/funkwhale_api/users/models.py b/api/funkwhale_api/users/models.py
index fcf78d0473..281fc4fe8d 100644
--- a/api/funkwhale_api/users/models.py
+++ b/api/funkwhale_api/users/models.py
@@ -17,26 +17,20 @@ from funkwhale_api.common import preferences
 
 
 def get_token():
-    return binascii.b2a_hex(os.urandom(15)).decode('utf-8')
+    return binascii.b2a_hex(os.urandom(15)).decode("utf-8")
 
 
 PERMISSIONS_CONFIGURATION = {
-    'federation': {
-        'label': 'Manage library federation',
-        'help_text': 'Follow other instances, accept/deny library follow requests...',
+    "federation": {
+        "label": "Manage library federation",
+        "help_text": "Follow other instances, accept/deny library follow requests...",
     },
-    'library': {
-        'label': 'Manage library',
-        'help_text': 'Manage library, delete files, tracks, artists, albums...',
-    },
-    'settings': {
-        'label': 'Manage instance-level settings',
-        'help_text': '',
-    },
-    'upload': {
-        'label': 'Upload new content to the library',
-        'help_text': '',
+    "library": {
+        "label": "Manage library",
+        "help_text": "Manage library, delete files, tracks, artists, albums...",
     },
+    "settings": {"label": "Manage instance-level settings", "help_text": ""},
+    "upload": {"label": "Upload new content to the library", "help_text": ""},
 }
 
 PERMISSIONS = sorted(PERMISSIONS_CONFIGURATION.keys())
@@ -58,51 +52,54 @@ class User(AbstractUser):
     # anyway since django use stronger schemes for storing passwords.
     # Users that want to use the subsonic API from external client
     # should set this token and use it as their password in such clients
-    subsonic_api_token = models.CharField(
-        blank=True, null=True, max_length=255)
+    subsonic_api_token = models.CharField(blank=True, null=True, max_length=255)
 
     # permissions
     permission_federation = models.BooleanField(
-        PERMISSIONS_CONFIGURATION['federation']['label'],
-        help_text=PERMISSIONS_CONFIGURATION['federation']['help_text'],
-        default=False)
+        PERMISSIONS_CONFIGURATION["federation"]["label"],
+        help_text=PERMISSIONS_CONFIGURATION["federation"]["help_text"],
+        default=False,
+    )
     permission_library = models.BooleanField(
-        PERMISSIONS_CONFIGURATION['library']['label'],
-        help_text=PERMISSIONS_CONFIGURATION['library']['help_text'],
-        default=False)
+        PERMISSIONS_CONFIGURATION["library"]["label"],
+        help_text=PERMISSIONS_CONFIGURATION["library"]["help_text"],
+        default=False,
+    )
     permission_settings = models.BooleanField(
-        PERMISSIONS_CONFIGURATION['settings']['label'],
-        help_text=PERMISSIONS_CONFIGURATION['settings']['help_text'],
-        default=False)
+        PERMISSIONS_CONFIGURATION["settings"]["label"],
+        help_text=PERMISSIONS_CONFIGURATION["settings"]["help_text"],
+        default=False,
+    )
     permission_upload = models.BooleanField(
-        PERMISSIONS_CONFIGURATION['upload']['label'],
-        help_text=PERMISSIONS_CONFIGURATION['upload']['help_text'],
-        default=False)
+        PERMISSIONS_CONFIGURATION["upload"]["label"],
+        help_text=PERMISSIONS_CONFIGURATION["upload"]["help_text"],
+        default=False,
+    )
 
     def __str__(self):
         return self.username
 
     def get_permissions(self):
-        defaults = preferences.get('users__default_permissions')
+        defaults = preferences.get("users__default_permissions")
         perms = {}
         for p in PERMISSIONS:
             v = (
-                self.is_superuser or
-                getattr(self, 'permission_{}'.format(p)) or
-                p in defaults
+                self.is_superuser
+                or getattr(self, "permission_{}".format(p))
+                or p in defaults
             )
             perms[p] = v
         return perms
 
-    def has_permissions(self, *perms, operator='and'):
-        if operator not in ['and', 'or']:
-            raise ValueError('Invalid operator {}'.format(operator))
+    def has_permissions(self, *perms, operator="and"):
+        if operator not in ["and", "or"]:
+            raise ValueError("Invalid operator {}".format(operator))
         permissions = self.get_permissions()
-        checker = all if operator == 'and' else any
+        checker = all if operator == "and" else any
         return checker([permissions[p] for p in perms])
 
     def get_absolute_url(self):
-        return reverse('users:detail', kwargs={'username': self.username})
+        return reverse("users:detail", kwargs={"username": self.username})
 
     def update_secret_key(self):
         self.secret_key = uuid.uuid4()
@@ -119,4 +116,4 @@ class User(AbstractUser):
             self.update_subsonic_api_token()
 
     def get_activity_url(self):
-        return settings.FUNKWHALE_URL + '/@{}'.format(self.username)
+        return settings.FUNKWHALE_URL + "/@{}".format(self.username)
diff --git a/api/funkwhale_api/users/permissions.py b/api/funkwhale_api/users/permissions.py
index 146bc5e1c2..02c1198e8c 100644
--- a/api/funkwhale_api/users/permissions.py
+++ b/api/funkwhale_api/users/permissions.py
@@ -11,11 +11,13 @@ class HasUserPermission(BasePermission):
         permission_classes = [HasUserPermission]
         required_permissions = ['federation']
     """
+
     def has_permission(self, request, view):
-        if not hasattr(request, 'user') or not request.user:
+        if not hasattr(request, "user") or not request.user:
             return False
         if request.user.is_anonymous:
             return False
-        operator = getattr(view, 'permission_operator', 'and')
+        operator = getattr(view, "permission_operator", "and")
         return request.user.has_permissions(
-            *view.required_permissions, operator=operator)
+            *view.required_permissions, operator=operator
+        )
diff --git a/api/funkwhale_api/users/rest_auth_urls.py b/api/funkwhale_api/users/rest_auth_urls.py
index fa6c425cc5..f5015ecbda 100644
--- a/api/funkwhale_api/users/rest_auth_urls.py
+++ b/api/funkwhale_api/users/rest_auth_urls.py
@@ -8,25 +8,30 @@ from . import views
 
 
 urlpatterns = [
-    url(r'^$', views.RegisterView.as_view(), name='rest_register'),
-    url(r'^verify-email/$',
+    url(r"^$", views.RegisterView.as_view(), name="rest_register"),
+    url(
+        r"^verify-email/$",
         registration_views.VerifyEmailView.as_view(),
-        name='rest_verify_email'),
-    url(r'^change-password/$',
+        name="rest_verify_email",
+    ),
+    url(
+        r"^change-password/$",
         rest_auth_views.PasswordChangeView.as_view(),
-        name='change_password'),
-
+        name="change_password",
+    ),
     # This url is used by django-allauth and empty TemplateView is
     # defined just to allow reverse() call inside app, for example when email
     # with verification link is being sent, then it's required to render email
     # content.
-
     # account_confirm_email - You should override this view to handle it in
     # your API client somehow and then, send post to /verify-email/ endpoint
     # with proper key.
     # If you don't want to use API on that step, then just use ConfirmEmailView
     # view from:
     # djang-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190
-    url(r'^account-confirm-email/(?P<key>\w+)/$', TemplateView.as_view(),
-        name='account_confirm_email'),
+    url(
+        r"^account-confirm-email/(?P<key>\w+)/$",
+        TemplateView.as_view(),
+        name="account_confirm_email",
+    ),
 ]
diff --git a/api/funkwhale_api/users/serializers.py b/api/funkwhale_api/users/serializers.py
index 3a095e78aa..ab289bc515 100644
--- a/api/funkwhale_api/users/serializers.py
+++ b/api/funkwhale_api/users/serializers.py
@@ -9,35 +9,27 @@ from . import models
 
 class UserActivitySerializer(activity_serializers.ModelSerializer):
     type = serializers.SerializerMethodField()
-    name = serializers.CharField(source='username')
-    local_id = serializers.CharField(source='username')
+    name = serializers.CharField(source="username")
+    local_id = serializers.CharField(source="username")
 
     class Meta:
         model = models.User
-        fields = [
-            'id',
-            'local_id',
-            'name',
-            'type'
-        ]
+        fields = ["id", "local_id", "name", "type"]
 
     def get_type(self, obj):
-        return 'Person'
+        return "Person"
 
 
 class UserBasicSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.User
-        fields = ['id', 'username', 'name', 'date_joined']
+        fields = ["id", "username", "name", "date_joined"]
 
 
 class UserWriteSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.User
-        fields = [
-            'name',
-            'privacy_level'
-        ]
+        fields = ["name", "privacy_level"]
 
 
 class UserReadSerializer(serializers.ModelSerializer):
@@ -47,15 +39,15 @@ class UserReadSerializer(serializers.ModelSerializer):
     class Meta:
         model = models.User
         fields = [
-            'id',
-            'username',
-            'name',
-            'email',
-            'is_staff',
-            'is_superuser',
-            'permissions',
-            'date_joined',
-            'privacy_level',
+            "id",
+            "username",
+            "name",
+            "email",
+            "is_staff",
+            "is_superuser",
+            "permissions",
+            "date_joined",
+            "privacy_level",
         ]
 
     def get_permissions(self, o):
@@ -64,8 +56,4 @@ class UserReadSerializer(serializers.ModelSerializer):
 
 class PasswordResetSerializer(PRS):
     def get_email_options(self):
-        return {
-            'extra_email_context': {
-                'funkwhale_url': settings.FUNKWHALE_URL
-            }
-        }
+        return {"extra_email_context": {"funkwhale_url": settings.FUNKWHALE_URL}}
diff --git a/api/funkwhale_api/users/views.py b/api/funkwhale_api/users/views.py
index 0cc3178891..dfb71dd058 100644
--- a/api/funkwhale_api/users/views.py
+++ b/api/funkwhale_api/users/views.py
@@ -13,12 +13,9 @@ from . import serializers
 
 
 class RegisterView(BaseRegisterView):
-
     def create(self, request, *args, **kwargs):
         if not self.is_open_for_signup(request):
-            r = {
-                'detail': 'Registration has been disabled',
-            }
+            r = {"detail": "Registration has been disabled"}
             return Response(r, status=403)
         return super().create(request, *args, **kwargs)
 
@@ -26,47 +23,42 @@ class RegisterView(BaseRegisterView):
         return get_adapter().is_open_for_signup(request)
 
 
-class UserViewSet(
-        mixins.UpdateModelMixin,
-        viewsets.GenericViewSet):
+class UserViewSet(mixins.UpdateModelMixin, viewsets.GenericViewSet):
     queryset = models.User.objects.all()
     serializer_class = serializers.UserWriteSerializer
-    lookup_field = 'username'
+    lookup_field = "username"
 
-    @list_route(methods=['get'])
+    @list_route(methods=["get"])
     def me(self, request, *args, **kwargs):
         """Return information about the current user"""
         serializer = serializers.UserReadSerializer(request.user)
         return Response(serializer.data)
 
-    @detail_route(
-        methods=['get', 'post', 'delete'], url_path='subsonic-token')
+    @detail_route(methods=["get", "post", "delete"], url_path="subsonic-token")
     def subsonic_token(self, request, *args, **kwargs):
-        if not self.request.user.username == kwargs.get('username'):
+        if not self.request.user.username == kwargs.get("username"):
             return Response(status=403)
-        if not preferences.get('subsonic__enabled'):
+        if not preferences.get("subsonic__enabled"):
             return Response(status=405)
-        if request.method.lower() == 'get':
-            return Response({
-                'subsonic_api_token': self.request.user.subsonic_api_token
-            })
-        if request.method.lower() == 'delete':
+        if request.method.lower() == "get":
+            return Response(
+                {"subsonic_api_token": self.request.user.subsonic_api_token}
+            )
+        if request.method.lower() == "delete":
             self.request.user.subsonic_api_token = None
-            self.request.user.save(update_fields=['subsonic_api_token'])
+            self.request.user.save(update_fields=["subsonic_api_token"])
             return Response(status=204)
         self.request.user.update_subsonic_api_token()
-        self.request.user.save(update_fields=['subsonic_api_token'])
-        data = {
-            'subsonic_api_token': self.request.user.subsonic_api_token
-        }
+        self.request.user.save(update_fields=["subsonic_api_token"])
+        data = {"subsonic_api_token": self.request.user.subsonic_api_token}
         return Response(data)
 
     def update(self, request, *args, **kwargs):
-        if not self.request.user.username == kwargs.get('username'):
+        if not self.request.user.username == kwargs.get("username"):
             return Response(status=403)
         return super().update(request, *args, **kwargs)
 
     def partial_update(self, request, *args, **kwargs):
-        if not self.request.user.username == kwargs.get('username'):
+        if not self.request.user.username == kwargs.get("username"):
             return Response(status=403)
         return super().partial_update(request, *args, **kwargs)
diff --git a/api/tests/activity/test_record.py b/api/tests/activity/test_record.py
index 41846ba6f1..98cefaba21 100644
--- a/api/tests/activity/test_record.py
+++ b/api/tests/activity/test_record.py
@@ -8,36 +8,35 @@ from funkwhale_api.activity import record
 
 class FakeModel(models.Model):
     class Meta:
-        app_label = 'tests'
+        app_label = "tests"
 
 
 class FakeSerializer(serializers.ModelSerializer):
     class Meta:
         model = FakeModel
-        fields = ['id']
-
-
+        fields = ["id"]
 
 
 def test_can_bind_serializer_to_model(activity_registry):
     activity_registry.register_serializer(FakeSerializer)
 
-    assert activity_registry['tests.FakeModel']['serializer'] == FakeSerializer
+    assert activity_registry["tests.FakeModel"]["serializer"] == FakeSerializer
 
 
 def test_can_bind_consumer_to_model(activity_registry):
     activity_registry.register_serializer(FakeSerializer)
-    @activity_registry.register_consumer('tests.FakeModel')
+
+    @activity_registry.register_consumer("tests.FakeModel")
     def propagate(data, obj):
         return True
 
-    assert activity_registry['tests.FakeModel']['consumers'] == [propagate]
+    assert activity_registry["tests.FakeModel"]["consumers"] == [propagate]
 
 
 def test_record_object_calls_consumer(activity_registry, mocker):
     activity_registry.register_serializer(FakeSerializer)
     stub = mocker.stub()
-    activity_registry.register_consumer('tests.FakeModel')(stub)
+    activity_registry.register_consumer("tests.FakeModel")(stub)
     o = FakeModel(id=1)
     data = FakeSerializer(o).data
     record.send(o)
diff --git a/api/tests/activity/test_serializers.py b/api/tests/activity/test_serializers.py
index 792fa74b9c..2561b5c8c5 100644
--- a/api/tests/activity/test_serializers.py
+++ b/api/tests/activity/test_serializers.py
@@ -1,12 +1,11 @@
 from funkwhale_api.activity import serializers
 from funkwhale_api.favorites.serializers import TrackFavoriteActivitySerializer
-from funkwhale_api.history.serializers import \
-    ListeningActivitySerializer
+from funkwhale_api.history.serializers import ListeningActivitySerializer
 
 
 def test_autoserializer(factories):
-    favorite = factories['favorites.TrackFavorite']()
-    listening = factories['history.Listening']()
+    favorite = factories["favorites.TrackFavorite"]()
+    listening = factories["history.Listening"]()
     objects = [favorite, listening]
     serializer = serializers.AutoSerializer(objects, many=True)
     expected = [
diff --git a/api/tests/activity/test_utils.py b/api/tests/activity/test_utils.py
index 43bb45df84..b12e3d0cb3 100644
--- a/api/tests/activity/test_utils.py
+++ b/api/tests/activity/test_utils.py
@@ -2,20 +2,18 @@ from funkwhale_api.activity import utils
 
 
 def test_get_activity(factories):
-    user = factories['users.User']()
-    listening = factories['history.Listening']()
-    favorite = factories['favorites.TrackFavorite']()
+    user = factories["users.User"]()
+    listening = factories["history.Listening"]()
+    favorite = factories["favorites.TrackFavorite"]()
 
     objects = list(utils.get_activity(user))
     assert objects == [favorite, listening]
 
 
 def test_get_activity_honors_privacy_level(factories, anonymous_user):
-    listening = factories['history.Listening'](user__privacy_level='me')
-    favorite1 = factories['favorites.TrackFavorite'](
-        user__privacy_level='everyone')
-    favorite2 = factories['favorites.TrackFavorite'](
-        user__privacy_level='instance')
+    listening = factories["history.Listening"](user__privacy_level="me")
+    favorite1 = factories["favorites.TrackFavorite"](user__privacy_level="everyone")
+    favorite2 = factories["favorites.TrackFavorite"](user__privacy_level="instance")
 
     objects = list(utils.get_activity(anonymous_user))
     assert objects == [favorite1]
diff --git a/api/tests/activity/test_views.py b/api/tests/activity/test_views.py
index 9b24f3ad3a..188039e664 100644
--- a/api/tests/activity/test_views.py
+++ b/api/tests/activity/test_views.py
@@ -5,14 +5,13 @@ from funkwhale_api.activity import utils
 
 
 def test_activity_view(factories, api_client, preferences, anonymous_user):
-    preferences['common__api_authentication_required'] = False
-    favorite = factories['favorites.TrackFavorite'](
-        user__privacy_level='everyone')
-    listening = factories['history.Listening']()
-    url = reverse('api:v1:activity-list')
+    preferences["common__api_authentication_required"] = False
+    favorite = factories["favorites.TrackFavorite"](user__privacy_level="everyone")
+    listening = factories["history.Listening"]()
+    url = reverse("api:v1:activity-list")
     objects = utils.get_activity(anonymous_user)
     serializer = serializers.AutoSerializer(objects, many=True)
     response = api_client.get(url)
 
     assert response.status_code == 200
-    assert response.data['results'] == serializer.data
+    assert response.data["results"] == serializer.data
diff --git a/api/tests/channels/test_auth.py b/api/tests/channels/test_auth.py
index a2b7eaf0ca..19bdebe682 100644
--- a/api/tests/channels/test_auth.py
+++ b/api/tests/channels/test_auth.py
@@ -8,30 +8,24 @@ jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
 jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
 
 
-@pytest.mark.parametrize('query_string', [
-    b'token=wrong',
-    b'',
-])
+@pytest.mark.parametrize("query_string", [b"token=wrong", b""])
 def test_header_anonymous(query_string, factories):
     def callback(scope):
-        assert scope['user'].is_anonymous
+        assert scope["user"].is_anonymous
 
-    scope = {
-        'query_string': query_string
-    }
+    scope = {"query_string": query_string}
     consumer = TokenAuthMiddleware(callback)
     consumer(scope)
 
 
 def test_header_correct_token(factories):
-    user = factories['users.User']()
+    user = factories["users.User"]()
     payload = jwt_payload_handler(user)
     token = jwt_encode_handler(payload)
+
     def callback(scope):
-        assert scope['user'] == user
+        assert scope["user"] == user
 
-    scope = {
-        'query_string': 'token={}'.format(token).encode('utf-8')
-    }
+    scope = {"query_string": "token={}".format(token).encode("utf-8")}
     consumer = TokenAuthMiddleware(callback)
     consumer(scope)
diff --git a/api/tests/channels/test_consumers.py b/api/tests/channels/test_consumers.py
index f1648efb3a..f4f5f3ad6f 100644
--- a/api/tests/channels/test_consumers.py
+++ b/api/tests/channels/test_consumers.py
@@ -2,15 +2,15 @@ from funkwhale_api.common import consumers
 
 
 def test_auth_consumer_requires_valid_user(mocker):
-    m = mocker.patch('funkwhale_api.common.consumers.JsonAuthConsumer.close')
-    scope = {'user': None}
+    m = mocker.patch("funkwhale_api.common.consumers.JsonAuthConsumer.close")
+    scope = {"user": None}
     consumer = consumers.JsonAuthConsumer(scope=scope)
     consumer.connect()
     m.assert_called_once_with()
 
 
 def test_auth_consumer_requires_user_in_scope(mocker):
-    m = mocker.patch('funkwhale_api.common.consumers.JsonAuthConsumer.close')
+    m = mocker.patch("funkwhale_api.common.consumers.JsonAuthConsumer.close")
     scope = {}
     consumer = consumers.JsonAuthConsumer(scope=scope)
     consumer.connect()
@@ -18,9 +18,9 @@ def test_auth_consumer_requires_user_in_scope(mocker):
 
 
 def test_auth_consumer_accepts_connection(mocker, factories):
-    user = factories['users.User']()
-    m = mocker.patch('funkwhale_api.common.consumers.JsonAuthConsumer.accept')
-    scope = {'user': user}
+    user = factories["users.User"]()
+    m = mocker.patch("funkwhale_api.common.consumers.JsonAuthConsumer.accept")
+    scope = {"user": user}
     consumer = consumers.JsonAuthConsumer(scope=scope)
     consumer.connect()
     m.assert_called_once_with()
diff --git a/api/tests/common/test_fields.py b/api/tests/common/test_fields.py
index 29a8fb05c4..bd836f840b 100644
--- a/api/tests/common/test_fields.py
+++ b/api/tests/common/test_fields.py
@@ -7,11 +7,16 @@ from funkwhale_api.common import fields
 from funkwhale_api.users.factories import UserFactory
 
 
-@pytest.mark.parametrize('user,expected', [
-    (AnonymousUser(), Q(privacy_level='everyone')),
-    (UserFactory.build(pk=1),
-     Q(privacy_level__in=['followers', 'instance', 'everyone'])),
-])
-def test_privacy_level_query(user,expected):
+@pytest.mark.parametrize(
+    "user,expected",
+    [
+        (AnonymousUser(), Q(privacy_level="everyone")),
+        (
+            UserFactory.build(pk=1),
+            Q(privacy_level__in=["followers", "instance", "everyone"]),
+        ),
+    ],
+)
+def test_privacy_level_query(user, expected):
     query = fields.privacy_level_query(user)
     assert query == expected
diff --git a/api/tests/common/test_permissions.py b/api/tests/common/test_permissions.py
index f04f12e0b0..952ec589e7 100644
--- a/api/tests/common/test_permissions.py
+++ b/api/tests/common/test_permissions.py
@@ -8,36 +8,36 @@ from funkwhale_api.common import permissions
 
 
 def test_owner_permission_owner_field_ok(nodb_factories, api_request):
-    playlist = nodb_factories['playlists.Playlist']()
+    playlist = nodb_factories["playlists.Playlist"]()
     view = APIView.as_view()
     permission = permissions.OwnerPermission()
-    request = api_request.get('/')
-    setattr(request, 'user', playlist.user)
+    request = api_request.get("/")
+    setattr(request, "user", playlist.user)
     check = permission.has_object_permission(request, view, playlist)
 
     assert check is True
 
 
 def test_owner_permission_owner_field_not_ok(
-        anonymous_user, nodb_factories, api_request):
-    playlist = nodb_factories['playlists.Playlist']()
+    anonymous_user, nodb_factories, api_request
+):
+    playlist = nodb_factories["playlists.Playlist"]()
     view = APIView.as_view()
     permission = permissions.OwnerPermission()
-    request = api_request.get('/')
-    setattr(request, 'user', anonymous_user)
+    request = api_request.get("/")
+    setattr(request, "user", anonymous_user)
 
     with pytest.raises(Http404):
         permission.has_object_permission(request, view, playlist)
 
 
-def test_owner_permission_read_only(
-        anonymous_user, nodb_factories, api_request):
-    playlist = nodb_factories['playlists.Playlist']()
+def test_owner_permission_read_only(anonymous_user, nodb_factories, api_request):
+    playlist = nodb_factories["playlists.Playlist"]()
     view = APIView.as_view()
-    setattr(view, 'owner_checks', ['write'])
+    setattr(view, "owner_checks", ["write"])
     permission = permissions.OwnerPermission()
-    request = api_request.get('/')
-    setattr(request, 'user', anonymous_user)
+    request = api_request.get("/")
+    setattr(request, "user", anonymous_user)
     check = permission.has_object_permission(request, view, playlist)
 
     assert check is True
diff --git a/api/tests/common/test_preferences.py b/api/tests/common/test_preferences.py
index 475610a937..5ea398c87f 100644
--- a/api/tests/common/test_preferences.py
+++ b/api/tests/common/test_preferences.py
@@ -6,39 +6,40 @@ from funkwhale_api.common import preferences as common_preferences
 
 @pytest.fixture
 def string_list_pref(preferences):
-
     @global_preferences_registry.register
     class P(common_preferences.StringListPreference):
-        default = ['hello']
-        section = 'test'
-        name = 'string_list'
-    yield
-    del global_preferences_registry['test']['string_list']
+        default = ["hello"]
+        section = "test"
+        name = "string_list"
 
-
-@pytest.mark.parametrize('input,output', [
-    (['a', 'b', 'c'], 'a,b,c'),
-    (['a', 'c', 'b'], 'a,b,c'),
-    (('a', 'c', 'b'), 'a,b,c'),
-    ([], None),
-])
+    yield
+    del global_preferences_registry["test"]["string_list"]
+
+
+@pytest.mark.parametrize(
+    "input,output",
+    [
+        (["a", "b", "c"], "a,b,c"),
+        (["a", "c", "b"], "a,b,c"),
+        (("a", "c", "b"), "a,b,c"),
+        ([], None),
+    ],
+)
 def test_string_list_serializer_to_db(input, output):
     s = common_preferences.StringListSerializer.to_db(input) == output
 
 
-@pytest.mark.parametrize('input,output', [
-    ('a,b,c', ['a', 'b', 'c'], ),
-    (None, []),
-    ('', []),
-])
+@pytest.mark.parametrize(
+    "input,output", [("a,b,c", ["a", "b", "c"]), (None, []), ("", [])]
+)
 def test_string_list_serializer_to_python(input, output):
     s = common_preferences.StringListSerializer.to_python(input) == output
 
 
 def test_string_list_pref_default(string_list_pref, preferences):
-    assert preferences['test__string_list'] == ['hello']
+    assert preferences["test__string_list"] == ["hello"]
 
 
 def test_string_list_pref_set(string_list_pref, preferences):
-    preferences['test__string_list'] = ['world', 'hello']
-    assert preferences['test__string_list'] == ['hello', 'world']
+    preferences["test__string_list"] = ["world", "hello"]
+    assert preferences["test__string_list"] == ["hello", "world"]
diff --git a/api/tests/common/test_scripts.py b/api/tests/common/test_scripts.py
index ce478ba048..afedc3e3a3 100644
--- a/api/tests/common/test_scripts.py
+++ b/api/tests/common/test_scripts.py
@@ -9,38 +9,26 @@ def command():
     return script.Command()
 
 
-@pytest.mark.parametrize('script_name', [
-    'django_permissions_to_user_permissions',
-    'test',
-])
+@pytest.mark.parametrize(
+    "script_name", ["django_permissions_to_user_permissions", "test"]
+)
 def test_script_command_list(command, script_name, mocker):
-    mocked = mocker.patch(
-        'funkwhale_api.common.scripts.{}.main'.format(script_name))
+    mocked = mocker.patch("funkwhale_api.common.scripts.{}.main".format(script_name))
 
     command.handle(script_name=script_name, interactive=False)
 
-    mocked.assert_called_once_with(
-        command, script_name=script_name, interactive=False)
+    mocked.assert_called_once_with(command, script_name=script_name, interactive=False)
 
 
 def test_django_permissions_to_user_permissions(factories, command):
-    group = factories['auth.Group'](
+    group = factories["auth.Group"](perms=["federation.change_library"])
+    user1 = factories["users.User"](
         perms=[
-            'federation.change_library'
+            "dynamic_preferences.change_globalpreferencemodel",
+            "music.add_importbatch",
         ]
     )
-    user1 = factories['users.User'](
-        perms=[
-            'dynamic_preferences.change_globalpreferencemodel',
-            'music.add_importbatch',
-        ]
-    )
-    user2 = factories['users.User'](
-        perms=[
-            'music.add_importbatch',
-        ],
-        groups=[group]
-    )
+    user2 = factories["users.User"](perms=["music.add_importbatch"], groups=[group])
 
     scripts.django_permissions_to_user_permissions.main(command)
 
diff --git a/api/tests/common/test_serializers.py b/api/tests/common/test_serializers.py
index f0f5fb7e61..1bf13b1e02 100644
--- a/api/tests/common/test_serializers.py
+++ b/api/tests/common/test_serializers.py
@@ -7,20 +7,20 @@ from funkwhale_api.users import models
 class TestActionFilterSet(django_filters.FilterSet):
     class Meta:
         model = models.User
-        fields = ['is_active']
+        fields = ["is_active"]
 
 
 class TestSerializer(serializers.ActionSerializer):
-    actions = ['test']
+    actions = ["test"]
     filterset_class = TestActionFilterSet
 
     def handle_test(self, objects):
-        return {'hello': 'world'}
+        return {"hello": "world"}
 
 
 class TestDangerousSerializer(serializers.ActionSerializer):
-    actions = ['test', 'test_dangerous']
-    dangerous_actions = ['test_dangerous']
+    actions = ["test", "test_dangerous"]
+    dangerous_actions = ["test_dangerous"]
 
     def handle_test(self, objects):
         pass
@@ -30,107 +30,88 @@ class TestDangerousSerializer(serializers.ActionSerializer):
 
 
 def test_action_serializer_validates_action():
-    data = {'objects': 'all', 'action': 'nope'}
+    data = {"objects": "all", "action": "nope"}
     serializer = TestSerializer(data, queryset=models.User.objects.none())
 
     assert serializer.is_valid() is False
-    assert 'action' in serializer.errors
+    assert "action" in serializer.errors
 
 
 def test_action_serializer_validates_objects():
-    data = {'objects': 'nope', 'action': 'test'}
+    data = {"objects": "nope", "action": "test"}
     serializer = TestSerializer(data, queryset=models.User.objects.none())
 
     assert serializer.is_valid() is False
-    assert 'objects' in serializer.errors
+    assert "objects" in serializer.errors
 
 
 def test_action_serializers_objects_clean_ids(factories):
-    user1 = factories['users.User']()
-    user2 = factories['users.User']()
+    user1 = factories["users.User"]()
+    user2 = factories["users.User"]()
 
-    data = {'objects': [user1.pk], 'action': 'test'}
+    data = {"objects": [user1.pk], "action": "test"}
     serializer = TestSerializer(data, queryset=models.User.objects.all())
 
     assert serializer.is_valid() is True
-    assert list(serializer.validated_data['objects']) == [user1]
+    assert list(serializer.validated_data["objects"]) == [user1]
 
 
 def test_action_serializers_objects_clean_all(factories):
-    user1 = factories['users.User']()
-    user2 = factories['users.User']()
+    user1 = factories["users.User"]()
+    user2 = factories["users.User"]()
 
-    data = {'objects': 'all', 'action': 'test'}
+    data = {"objects": "all", "action": "test"}
     serializer = TestSerializer(data, queryset=models.User.objects.all())
 
     assert serializer.is_valid() is True
-    assert list(serializer.validated_data['objects']) == [user1, user2]
+    assert list(serializer.validated_data["objects"]) == [user1, user2]
 
 
 def test_action_serializers_save(factories, mocker):
-    handler = mocker.spy(TestSerializer, 'handle_test')
-    user1 = factories['users.User']()
-    user2 = factories['users.User']()
+    handler = mocker.spy(TestSerializer, "handle_test")
+    user1 = factories["users.User"]()
+    user2 = factories["users.User"]()
 
-    data = {'objects': 'all', 'action': 'test'}
+    data = {"objects": "all", "action": "test"}
     serializer = TestSerializer(data, queryset=models.User.objects.all())
 
     assert serializer.is_valid() is True
     result = serializer.save()
-    assert result == {
-        'updated': 2,
-        'action': 'test',
-        'result': {'hello': 'world'},
-    }
+    assert result == {"updated": 2, "action": "test", "result": {"hello": "world"}}
     handler.assert_called_once()
 
 
 def test_action_serializers_filterset(factories):
-    user1 = factories['users.User'](is_active=False)
-    user2 = factories['users.User'](is_active=True)
-
-    data = {
-        'objects': 'all',
-        'action': 'test',
-        'filters': {'is_active': True},
-    }
+    user1 = factories["users.User"](is_active=False)
+    user2 = factories["users.User"](is_active=True)
+
+    data = {"objects": "all", "action": "test", "filters": {"is_active": True}}
     serializer = TestSerializer(data, queryset=models.User.objects.all())
 
     assert serializer.is_valid() is True
-    assert list(serializer.validated_data['objects']) == [user2]
+    assert list(serializer.validated_data["objects"]) == [user2]
 
 
 def test_action_serializers_validates_at_least_one_object():
-    data = {
-        'objects': 'all',
-        'action': 'test',
-    }
+    data = {"objects": "all", "action": "test"}
     serializer = TestSerializer(data, queryset=models.User.objects.none())
 
     assert serializer.is_valid() is False
-    assert 'non_field_errors' in serializer.errors
+    assert "non_field_errors" in serializer.errors
 
 
 def test_dangerous_actions_refuses_all(factories):
-    factories['users.User']()
-    data = {
-        'objects': 'all',
-        'action': 'test_dangerous',
-    }
-    serializer = TestDangerousSerializer(
-        data, queryset=models.User.objects.all())
+    factories["users.User"]()
+    data = {"objects": "all", "action": "test_dangerous"}
+    serializer = TestDangerousSerializer(data, queryset=models.User.objects.all())
 
     assert serializer.is_valid() is False
-    assert 'non_field_errors' in serializer.errors
+    assert "non_field_errors" in serializer.errors
 
 
 def test_dangerous_actions_refuses_not_listed(factories):
-    factories['users.User']()
-    data = {
-        'objects': 'all',
-        'action': 'test',
-    }
-    serializer = TestDangerousSerializer(
-        data, queryset=models.User.objects.all())
+    factories["users.User"]()
+    data = {"objects": "all", "action": "test"}
+    serializer = TestDangerousSerializer(data, queryset=models.User.objects.all())
 
     assert serializer.is_valid() is True
diff --git a/api/tests/common/test_session.py b/api/tests/common/test_session.py
index 7ff1e660bc..eba26dae01 100644
--- a/api/tests/common/test_session.py
+++ b/api/tests/common/test_session.py
@@ -4,15 +4,14 @@ from funkwhale_api.common import session
 
 
 def test_get_user_agent(settings):
-    settings.FUNKWHALE_URL = 'https://test.com'
-    'http.rb/3.0.0 (Mastodon/2.2.0; +https://mastodon.eliotberriot.com/)'
-    expected = 'python-requests (funkwhale/{}; +{})'.format(
-        funkwhale_api.__version__,
-        settings.FUNKWHALE_URL
+    settings.FUNKWHALE_URL = "https://test.com"
+    "http.rb/3.0.0 (Mastodon/2.2.0; +https://mastodon.eliotberriot.com/)"
+    expected = "python-requests (funkwhale/{}; +{})".format(
+        funkwhale_api.__version__, settings.FUNKWHALE_URL
     )
     assert session.get_user_agent() == expected
 
 
 def test_get_session():
     expected = session.get_user_agent()
-    assert session.get_session().headers['User-Agent'] == expected
+    assert session.get_session().headers["User-Agent"] == expected
diff --git a/api/tests/conftest.py b/api/tests/conftest.py
index 7caff20099..080e1a927a 100644
--- a/api/tests/conftest.py
+++ b/api/tests/conftest.py
@@ -24,6 +24,7 @@ from funkwhale_api.taskapp import celery
 def factories_autodiscover():
     from django.apps import apps
     from funkwhale_api import factories
+
     app_names = [app.name for app in apps.app_configs.values()]
     factories.registry.autodiscover(app_names)
 
@@ -44,6 +45,7 @@ def factories(db):
     users.User or music.Track
     """
     from funkwhale_api import factories
+
     for v in factories.registry.values():
         try:
             v._meta.strategy = factory.CREATE_STRATEGY
@@ -60,6 +62,7 @@ def nodb_factories():
     that does not require access to the database
     """
     from funkwhale_api import factories
+
     for v in factories.registry.values():
         try:
             v._meta.strategy = factory.BUILD_STRATEGY
@@ -104,11 +107,11 @@ def logged_in_client(db, factories, client):
     Returns a logged-in, non-API client with an authenticated ``User``
     stored in the ``user`` attribute
     """
-    user = factories['users.User']()
-    assert client.login(username=user.username, password='test')
-    setattr(client, 'user', user)
+    user = factories["users.User"]()
+    assert client.login(username=user.username, password="test")
+    setattr(client, "user", user)
     yield client
-    delattr(client, 'user')
+    delattr(client, "user")
 
 
 @pytest.fixture
@@ -131,12 +134,12 @@ def logged_in_api_client(db, factories, api_client):
     Return a logged-in API client with an authenticated ``User``
     stored in the ``user`` attribute
     """
-    user = factories['users.User']()
-    assert api_client.login(username=user.username, password='test')
+    user = factories["users.User"]()
+    assert api_client.login(username=user.username, password="test")
     api_client.force_authenticate(user=user)
-    setattr(api_client, 'user', user)
+    setattr(api_client, "user", user)
     yield api_client
-    delattr(api_client, 'user')
+    delattr(api_client, "user")
 
 
 @pytest.fixture
@@ -145,11 +148,11 @@ def superuser_api_client(db, factories, api_client):
     Return a logged-in API client with an authenticated superuser
     stored in the ``user`` attribute
     """
-    user = factories['users.SuperUser']()
-    assert api_client.login(username=user.username, password='test')
-    setattr(api_client, 'user', user)
+    user = factories["users.SuperUser"]()
+    assert api_client.login(username=user.username, password="test")
+    setattr(api_client, "user", user)
     yield api_client
-    delattr(api_client, 'user')
+    delattr(api_client, "user")
 
 
 @pytest.fixture
@@ -158,11 +161,11 @@ def superuser_client(db, factories, client):
     Return a logged-in, non-API client with an authenticated ``User``
     stored in the ``user`` attribute
     """
-    user = factories['users.SuperUser']()
-    assert client.login(username=user.username, password='test')
-    setattr(client, 'user', user)
+    user = factories["users.SuperUser"]()
+    assert client.login(username=user.username, password="test")
+    setattr(client, "user", user)
     yield client
-    delattr(client, 'user')
+    delattr(client, "user")
 
 
 @pytest.fixture
@@ -193,7 +196,7 @@ def activity_registry():
 
 @pytest.fixture
 def activity_muted(activity_registry, mocker):
-    yield mocker.patch.object(record, 'send')
+    yield mocker.patch.object(record, "send")
 
 
 @pytest.fixture(autouse=True)
@@ -222,19 +225,21 @@ def authenticated_actor(factories, mocker):
     """
     Returns an authenticated ActivityPub actor
     """
-    actor = factories['federation.Actor']()
+    actor = factories["federation.Actor"]()
     mocker.patch(
-        'funkwhale_api.federation.authentication.SignatureAuthentication.authenticate_actor',
-        return_value=actor)
+        "funkwhale_api.federation.authentication.SignatureAuthentication.authenticate_actor",
+        return_value=actor,
+    )
     yield actor
 
 
 @pytest.fixture
 def assert_user_permission():
-    def inner(view, permissions, operator='and'):
+    def inner(view, permissions, operator="and"):
         assert HasUserPermission in view.permission_classes
-        assert getattr(view, 'permission_operator', 'and') == operator
+        assert getattr(view, "permission_operator", "and") == operator
         assert set(view.required_permissions) == set(permissions)
+
     return inner
 
 
@@ -247,5 +252,6 @@ def to_api_date():
         if isinstance(value, datetime.date):
             f = rest_fields.DateField()
             return f.to_representation(value)
-        raise ValueError('Invalid value: {}'.format(value))
+        raise ValueError("Invalid value: {}".format(value))
+
     return inner
diff --git a/api/tests/data/youtube.py b/api/tests/data/youtube.py
index a8372d4c96..d3303d2ad1 100644
--- a/api/tests/data/youtube.py
+++ b/api/tests/data/youtube.py
@@ -3,20 +3,14 @@
 search = {}
 
 
-search['8 bit adventure'] = {
-    "pageInfo": {
-        "totalResults": 1000000,
-        "resultsPerPage": 25
-    },
+search["8 bit adventure"] = {
+    "pageInfo": {"totalResults": 1000000, "resultsPerPage": 25},
     "nextPageToken": "CBkQAA",
-    "etag": "\"gMxXHe-zinKdE9lTnzKu8vjcmDI/1L34zetsKWv-raAFiz0MuT0SsfQ\"",
+    "etag": '"gMxXHe-zinKdE9lTnzKu8vjcmDI/1L34zetsKWv-raAFiz0MuT0SsfQ"',
     "items": [
         {
-            "id": {
-                "videoId": "0HxZn6CzOIo",
-                "kind": "youtube#video"
-            },
-            "etag": "\"gMxXHe-zinKdE9lTnzKu8vjcmDI/GxK-wHBWUYfrJsd1dijBPTufrVE\"",
+            "id": {"videoId": "0HxZn6CzOIo", "kind": "youtube#video"},
+            "etag": '"gMxXHe-zinKdE9lTnzKu8vjcmDI/GxK-wHBWUYfrJsd1dijBPTufrVE"',
             "snippet": {
                 "liveBroadcastContent": "none",
                 "description": "Make sure to apply adhesive evenly before use. GET IT HERE: http://adhesivewombat.bandcamp.com/album/marsupial-madness Facebook: ...",
@@ -28,28 +22,25 @@ search['8 bit adventure'] = {
                     "medium": {
                         "url": "https://i.ytimg.com/vi/0HxZn6CzOIo/mqdefault.jpg",
                         "height": 180,
-                        "width": 320
+                        "width": 320,
                     },
                     "high": {
                         "url": "https://i.ytimg.com/vi/0HxZn6CzOIo/hqdefault.jpg",
                         "height": 360,
-                        "width": 480
+                        "width": 480,
                     },
                     "default": {
                         "url": "https://i.ytimg.com/vi/0HxZn6CzOIo/default.jpg",
                         "height": 90,
-                        "width": 120
-                    }
-                }
+                        "width": 120,
+                    },
+                },
             },
-            "kind": "youtube#searchResult"
+            "kind": "youtube#searchResult",
         },
         {
-            "id": {
-                "videoId": "n4A_F5SXmgo",
-                "kind": "youtube#video"
-            },
-            "etag": "\"gMxXHe-zinKdE9lTnzKu8vjcmDI/aRVESw24jlgiErDgJKxNrazKRDc\"",
+            "id": {"videoId": "n4A_F5SXmgo", "kind": "youtube#video"},
+            "etag": '"gMxXHe-zinKdE9lTnzKu8vjcmDI/aRVESw24jlgiErDgJKxNrazKRDc"',
             "snippet": {
                 "liveBroadcastContent": "none",
                 "description": "Free Download: http://bit.ly/1fZ1pMJ I don't post 8 bit'ish music much but damn I must admit this is goood! Enjoy \u2665 \u25bbSpikedGrin: ...",
@@ -61,34 +52,31 @@ search['8 bit adventure'] = {
                     "medium": {
                         "url": "https://i.ytimg.com/vi/n4A_F5SXmgo/mqdefault.jpg",
                         "height": 180,
-                        "width": 320
+                        "width": 320,
                     },
                     "high": {
                         "url": "https://i.ytimg.com/vi/n4A_F5SXmgo/hqdefault.jpg",
                         "height": 360,
-                        "width": 480
+                        "width": 480,
                     },
                     "default": {
                         "url": "https://i.ytimg.com/vi/n4A_F5SXmgo/default.jpg",
                         "height": 90,
-                        "width": 120
-                    }
-                }
+                        "width": 120,
+                    },
+                },
             },
-            "kind": "youtube#searchResult"
+            "kind": "youtube#searchResult",
         },
     ],
     "regionCode": "FR",
-    "kind": "youtube#searchListResponse"
+    "kind": "youtube#searchListResponse",
 }
 
-search['system of a down toxicity'] = {
+search["system of a down toxicity"] = {
     "items": [
         {
-            "id": {
-                "kind": "youtube#video",
-                "videoId": "BorYwGi2SJc"
-            },
+            "id": {"kind": "youtube#video", "videoId": "BorYwGi2SJc"},
             "kind": "youtube#searchResult",
             "snippet": {
                 "title": "System of a Down: Toxicity",
@@ -98,30 +86,27 @@ search['system of a down toxicity'] = {
                     "default": {
                         "height": 90,
                         "width": 120,
-                        "url": "https://i.ytimg.com/vi/BorYwGi2SJc/default.jpg"
+                        "url": "https://i.ytimg.com/vi/BorYwGi2SJc/default.jpg",
                     },
                     "high": {
                         "height": 360,
                         "width": 480,
-                        "url": "https://i.ytimg.com/vi/BorYwGi2SJc/hqdefault.jpg"
+                        "url": "https://i.ytimg.com/vi/BorYwGi2SJc/hqdefault.jpg",
                     },
                     "medium": {
                         "height": 180,
                         "width": 320,
-                        "url": "https://i.ytimg.com/vi/BorYwGi2SJc/mqdefault.jpg"
-                    }
+                        "url": "https://i.ytimg.com/vi/BorYwGi2SJc/mqdefault.jpg",
+                    },
                 },
                 "publishedAt": "2007-12-17T12:39:54.000Z",
                 "description": "http://www.vedrescsaba.uw.hu The System of a Down song Toxicity arranged for a classical piano quintet, played by Vedres Csaba and the Kairosz quartet.",
-                "liveBroadcastContent": "none"
+                "liveBroadcastContent": "none",
             },
-            "etag": "\"gMxXHe-zinKdE9lTnzKu8vjcmDI/UwR8H6P6kbijNZmBNkYd2jAzDnI\""
+            "etag": '"gMxXHe-zinKdE9lTnzKu8vjcmDI/UwR8H6P6kbijNZmBNkYd2jAzDnI"',
         },
         {
-            "id": {
-                "kind": "youtube#video",
-                "videoId": "ENBv2i88g6Y"
-            },
+            "id": {"kind": "youtube#video", "videoId": "ENBv2i88g6Y"},
             "kind": "youtube#searchResult",
             "snippet": {
                 "title": "System Of A Down - Question!",
@@ -131,32 +116,29 @@ search['system of a down toxicity'] = {
                     "default": {
                         "height": 90,
                         "width": 120,
-                        "url": "https://i.ytimg.com/vi/ENBv2i88g6Y/default.jpg"
+                        "url": "https://i.ytimg.com/vi/ENBv2i88g6Y/default.jpg",
                     },
                     "high": {
                         "height": 360,
                         "width": 480,
-                        "url": "https://i.ytimg.com/vi/ENBv2i88g6Y/hqdefault.jpg"
+                        "url": "https://i.ytimg.com/vi/ENBv2i88g6Y/hqdefault.jpg",
                     },
                     "medium": {
                         "height": 180,
                         "width": 320,
-                        "url": "https://i.ytimg.com/vi/ENBv2i88g6Y/mqdefault.jpg"
-                    }
+                        "url": "https://i.ytimg.com/vi/ENBv2i88g6Y/mqdefault.jpg",
+                    },
                 },
                 "publishedAt": "2009-10-03T04:49:03.000Z",
                 "description": "System of a Down's official music video for 'Question!'. Click to listen to System of a Down on Spotify: http://smarturl.it/SystemSpotify?IQid=SystemQu As featured ...",
-                "liveBroadcastContent": "none"
+                "liveBroadcastContent": "none",
             },
-            "etag": "\"gMxXHe-zinKdE9lTnzKu8vjcmDI/dB-M0N9mB4xE-k4yAF_4d8aU0I4\""
+            "etag": '"gMxXHe-zinKdE9lTnzKu8vjcmDI/dB-M0N9mB4xE-k4yAF_4d8aU0I4"',
         },
     ],
-    "etag": "\"gMxXHe-zinKdE9lTnzKu8vjcmDI/yhLQgSpeObNnybd5JqSzlGiJ8Ew\"",
+    "etag": '"gMxXHe-zinKdE9lTnzKu8vjcmDI/yhLQgSpeObNnybd5JqSzlGiJ8Ew"',
     "nextPageToken": "CBkQAA",
-    "pageInfo": {
-        "resultsPerPage": 25,
-        "totalResults": 26825
-    },
+    "pageInfo": {"resultsPerPage": 25, "totalResults": 26825},
     "kind": "youtube#searchListResponse",
-    "regionCode": "FR"
+    "regionCode": "FR",
 }
diff --git a/api/tests/favorites/test_activity.py b/api/tests/favorites/test_activity.py
index 63174f9e26..a6eff99912 100644
--- a/api/tests/favorites/test_activity.py
+++ b/api/tests/favorites/test_activity.py
@@ -5,15 +5,14 @@ from funkwhale_api.favorites import activities
 
 
 def test_get_favorite_activity_url(settings, factories):
-    favorite = factories['favorites.TrackFavorite']()
+    favorite = factories["favorites.TrackFavorite"]()
     user_url = favorite.user.get_activity_url()
-    expected = '{}/favorites/tracks/{}'.format(
-        user_url, favorite.pk)
+    expected = "{}/favorites/tracks/{}".format(user_url, favorite.pk)
     assert favorite.get_activity_url() == expected
 
 
 def test_activity_favorite_serializer(factories):
-    favorite = factories['favorites.TrackFavorite']()
+    favorite = factories["favorites.TrackFavorite"]()
 
     actor = UserActivitySerializer(favorite.user).data
     field = serializers.serializers.DateTimeField()
@@ -32,44 +31,31 @@ def test_activity_favorite_serializer(factories):
 
 
 def test_track_favorite_serializer_is_connected(activity_registry):
-    conf = activity_registry['favorites.TrackFavorite']
-    assert conf['serializer'] == serializers.TrackFavoriteActivitySerializer
+    conf = activity_registry["favorites.TrackFavorite"]
+    assert conf["serializer"] == serializers.TrackFavoriteActivitySerializer
 
 
-def test_track_favorite_serializer_instance_activity_consumer(
-        activity_registry):
-    conf = activity_registry['favorites.TrackFavorite']
+def test_track_favorite_serializer_instance_activity_consumer(activity_registry):
+    conf = activity_registry["favorites.TrackFavorite"]
     consumer = activities.broadcast_track_favorite_to_instance_activity
-    assert consumer in conf['consumers']
+    assert consumer in conf["consumers"]
 
 
-def test_broadcast_track_favorite_to_instance_activity(
-        factories, mocker):
-    p = mocker.patch('funkwhale_api.common.channels.group_send')
-    favorite = factories['favorites.TrackFavorite']()
+def test_broadcast_track_favorite_to_instance_activity(factories, mocker):
+    p = mocker.patch("funkwhale_api.common.channels.group_send")
+    favorite = factories["favorites.TrackFavorite"]()
     data = serializers.TrackFavoriteActivitySerializer(favorite).data
     consumer = activities.broadcast_track_favorite_to_instance_activity
-    message = {
-        "type": 'event.send',
-        "text": '',
-        "data": data
-    }
+    message = {"type": "event.send", "text": "", "data": data}
     consumer(data=data, obj=favorite)
-    p.assert_called_once_with('instance_activity', message)
+    p.assert_called_once_with("instance_activity", message)
 
 
-def test_broadcast_track_favorite_to_instance_activity_private(
-        factories, mocker):
-    p = mocker.patch('funkwhale_api.common.channels.group_send')
-    favorite = factories['favorites.TrackFavorite'](
-        user__privacy_level='me'
-    )
+def test_broadcast_track_favorite_to_instance_activity_private(factories, mocker):
+    p = mocker.patch("funkwhale_api.common.channels.group_send")
+    favorite = factories["favorites.TrackFavorite"](user__privacy_level="me")
     data = serializers.TrackFavoriteActivitySerializer(favorite).data
     consumer = activities.broadcast_track_favorite_to_instance_activity
-    message = {
-        "type": 'event.send',
-        "text": '',
-        "data": data
-    }
+    message = {"type": "event.send", "text": "", "data": data}
     consumer(data=data, obj=favorite)
     p.assert_not_called()
diff --git a/api/tests/favorites/test_favorites.py b/api/tests/favorites/test_favorites.py
index 591fe7c9c8..345c24bdee 100644
--- a/api/tests/favorites/test_favorites.py
+++ b/api/tests/favorites/test_favorites.py
@@ -6,10 +6,9 @@ from funkwhale_api.music.models import Track, Artist
 from funkwhale_api.favorites.models import TrackFavorite
 
 
-
 def test_user_can_add_favorite(factories):
-    track = factories['music.Track']()
-    user = factories['users.User']()
+    track = factories["music.Track"]()
+    user = factories["users.User"]()
     f = TrackFavorite.add(track, user)
 
     assert f.track == track
@@ -17,35 +16,34 @@ def test_user_can_add_favorite(factories):
 
 
 def test_user_can_get_his_favorites(factories, logged_in_client, client):
-    favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user)
-    url = reverse('api:v1:favorites:tracks-list')
+    favorite = factories["favorites.TrackFavorite"](user=logged_in_client.user)
+    url = reverse("api:v1:favorites:tracks-list")
     response = logged_in_client.get(url)
 
     expected = [
         {
-            'track': favorite.track.pk,
-            'id': favorite.id,
-            'creation_date': favorite.creation_date.isoformat().replace('+00:00', 'Z'),
+            "track": favorite.track.pk,
+            "id": favorite.id,
+            "creation_date": favorite.creation_date.isoformat().replace("+00:00", "Z"),
         }
     ]
-    parsed_json = json.loads(response.content.decode('utf-8'))
+    parsed_json = json.loads(response.content.decode("utf-8"))
 
-    assert expected == parsed_json['results']
+    assert expected == parsed_json["results"]
 
 
-def test_user_can_add_favorite_via_api(
-        factories, logged_in_client, activity_muted):
-    track = factories['music.Track']()
-    url = reverse('api:v1:favorites:tracks-list')
-    response = logged_in_client.post(url, {'track': track.pk})
+def test_user_can_add_favorite_via_api(factories, logged_in_client, activity_muted):
+    track = factories["music.Track"]()
+    url = reverse("api:v1:favorites:tracks-list")
+    response = logged_in_client.post(url, {"track": track.pk})
 
-    favorite = TrackFavorite.objects.latest('id')
+    favorite = TrackFavorite.objects.latest("id")
     expected = {
-        'track': track.pk,
-        'id': favorite.id,
-        'creation_date': favorite.creation_date.isoformat().replace('+00:00', 'Z'),
+        "track": track.pk,
+        "id": favorite.id,
+        "creation_date": favorite.creation_date.isoformat().replace("+00:00", "Z"),
     }
-    parsed_json = json.loads(response.content.decode('utf-8'))
+    parsed_json = json.loads(response.content.decode("utf-8"))
 
     assert expected == parsed_json
     assert favorite.track == track
@@ -53,18 +51,19 @@ def test_user_can_add_favorite_via_api(
 
 
 def test_adding_favorites_calls_activity_record(
-        factories, logged_in_client, activity_muted):
-    track = factories['music.Track']()
-    url = reverse('api:v1:favorites:tracks-list')
-    response = logged_in_client.post(url, {'track': track.pk})
+    factories, logged_in_client, activity_muted
+):
+    track = factories["music.Track"]()
+    url = reverse("api:v1:favorites:tracks-list")
+    response = logged_in_client.post(url, {"track": track.pk})
 
-    favorite = TrackFavorite.objects.latest('id')
+    favorite = TrackFavorite.objects.latest("id")
     expected = {
-        'track': track.pk,
-        'id': favorite.id,
-        'creation_date': favorite.creation_date.isoformat().replace('+00:00', 'Z'),
+        "track": track.pk,
+        "id": favorite.id,
+        "creation_date": favorite.creation_date.isoformat().replace("+00:00", "Z"),
     }
-    parsed_json = json.loads(response.content.decode('utf-8'))
+    parsed_json = json.loads(response.content.decode("utf-8"))
 
     assert expected == parsed_json
     assert favorite.track == track
@@ -74,44 +73,42 @@ def test_adding_favorites_calls_activity_record(
 
 
 def test_user_can_remove_favorite_via_api(logged_in_client, factories, client):
-    favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user)
-    url = reverse('api:v1:favorites:tracks-detail', kwargs={'pk': favorite.pk})
-    response = client.delete(url, {'track': favorite.track.pk})
+    favorite = factories["favorites.TrackFavorite"](user=logged_in_client.user)
+    url = reverse("api:v1:favorites:tracks-detail", kwargs={"pk": favorite.pk})
+    response = client.delete(url, {"track": favorite.track.pk})
     assert response.status_code == 204
     assert TrackFavorite.objects.count() == 0
 
 
-@pytest.mark.parametrize('method', ['delete', 'post'])
+@pytest.mark.parametrize("method", ["delete", "post"])
 def test_user_can_remove_favorite_via_api_using_track_id(
-        method, factories, logged_in_client):
-    favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user)
+    method, factories, logged_in_client
+):
+    favorite = factories["favorites.TrackFavorite"](user=logged_in_client.user)
 
-    url = reverse('api:v1:favorites:tracks-remove')
+    url = reverse("api:v1:favorites:tracks-remove")
     response = getattr(logged_in_client, method)(
-        url, json.dumps({'track': favorite.track.pk}),
-        content_type='application/json'
+        url, json.dumps({"track": favorite.track.pk}), content_type="application/json"
     )
 
     assert response.status_code == 204
     assert TrackFavorite.objects.count() == 0
 
 
-@pytest.mark.parametrize('url,method', [
-    ('api:v1:favorites:tracks-list', 'get'),
-])
+@pytest.mark.parametrize("url,method", [("api:v1:favorites:tracks-list", "get")])
 def test_url_require_auth(url, method, db, preferences, client):
-    preferences['common__api_authentication_required'] = True
+    preferences["common__api_authentication_required"] = True
     url = reverse(url)
     response = getattr(client, method)(url)
     assert response.status_code == 401
 
 
 def test_can_filter_tracks_by_favorites(factories, logged_in_client):
-    favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user)
+    favorite = factories["favorites.TrackFavorite"](user=logged_in_client.user)
 
-    url = reverse('api:v1:tracks-list')
-    response = logged_in_client.get(url, data={'favorites': True})
+    url = reverse("api:v1:tracks-list")
+    response = logged_in_client.get(url, data={"favorites": True})
 
-    parsed_json = json.loads(response.content.decode('utf-8'))
-    assert parsed_json['count'] == 1
-    assert parsed_json['results'][0]['id'] == favorite.track.id
+    parsed_json = json.loads(response.content.decode("utf-8"))
+    assert parsed_json["count"] == 1
+    assert parsed_json["results"][0]["id"] == favorite.track.id
diff --git a/api/tests/federation/test_activity.py b/api/tests/federation/test_activity.py
index c2673ff3bd..8e9000ce01 100644
--- a/api/tests/federation/test_activity.py
+++ b/api/tests/federation/test_activity.py
@@ -6,41 +6,34 @@ from funkwhale_api.federation import serializers
 
 def test_deliver(factories, r_mock, mocker, settings):
     settings.CELERY_TASK_ALWAYS_EAGER = True
-    to = factories['federation.Actor']()
-    mocker.patch(
-        'funkwhale_api.federation.actors.get_actor',
-        return_value=to)
-    sender = factories['federation.Actor']()
+    to = factories["federation.Actor"]()
+    mocker.patch("funkwhale_api.federation.actors.get_actor", return_value=to)
+    sender = factories["federation.Actor"]()
     ac = {
-        'id': 'http://test.federation/activity',
-        'type': 'Create',
-        'actor': sender.url,
-        'object': {
-            'id': 'http://test.federation/note',
-            'type': 'Note',
-            'content': 'Hello',
-        }
+        "id": "http://test.federation/activity",
+        "type": "Create",
+        "actor": sender.url,
+        "object": {
+            "id": "http://test.federation/note",
+            "type": "Note",
+            "content": "Hello",
+        },
     }
 
     r_mock.post(to.inbox_url)
 
-    activity.deliver(
-        ac,
-        to=[to.url],
-        on_behalf_of=sender,
-    )
+    activity.deliver(ac, to=[to.url], on_behalf_of=sender)
     request = r_mock.request_history[0]
 
     assert r_mock.called is True
     assert r_mock.call_count == 1
     assert request.url == to.inbox_url
-    assert request.headers['content-type'] == 'application/activity+json'
+    assert request.headers["content-type"] == "application/activity+json"
 
 
 def test_accept_follow(mocker, factories):
-    deliver = mocker.patch(
-        'funkwhale_api.federation.activity.deliver')
-    follow = factories['federation.Follow'](approved=None)
+    deliver = mocker.patch("funkwhale_api.federation.activity.deliver")
+    follow = factories["federation.Follow"](approved=None)
     expected_accept = serializers.AcceptFollowSerializer(follow).data
     activity.accept_follow(follow)
     deliver.assert_called_once_with(
diff --git a/api/tests/federation/test_actors.py b/api/tests/federation/test_actors.py
index 6f73a9b9b2..99c71b3482 100644
--- a/api/tests/federation/test_actors.py
+++ b/api/tests/federation/test_actors.py
@@ -18,11 +18,11 @@ from funkwhale_api.music import tasks as music_tasks
 
 def test_actor_fetching(r_mock):
     payload = {
-        'id': 'https://actor.mock/users/actor#main-key',
-        'owner': 'test',
-        'publicKeyPem': 'test_pem',
+        "id": "https://actor.mock/users/actor#main-key",
+        "owner": "test",
+        "publicKeyPem": "test_pem",
     }
-    actor_url = 'https://actor.mock/'
+    actor_url = "https://actor.mock/"
     r_mock.get(actor_url, json=payload)
     r = actors.get_actor_data(actor_url)
 
@@ -30,7 +30,7 @@ def test_actor_fetching(r_mock):
 
 
 def test_get_actor(factories, r_mock):
-    actor = factories['federation.Actor'].build()
+    actor = factories["federation.Actor"].build()
     payload = serializers.ActorSerializer(actor).data
     r_mock.get(actor.url, json=payload)
     new_actor = actors.get_actor(actor.url)
@@ -40,9 +40,9 @@ def test_get_actor(factories, r_mock):
 
 
 def test_get_actor_use_existing(factories, preferences, mocker):
-    preferences['federation__actor_fetch_delay'] = 60
-    actor = factories['federation.Actor']()
-    get_data = mocker.patch('funkwhale_api.federation.actors.get_actor_data')
+    preferences["federation__actor_fetch_delay"] = 60
+    actor = factories["federation.Actor"]()
+    get_data = mocker.patch("funkwhale_api.federation.actors.get_actor_data")
     new_actor = actors.get_actor(actor.url)
 
     assert new_actor == actor
@@ -50,87 +50,83 @@ def test_get_actor_use_existing(factories, preferences, mocker):
 
 
 def test_get_actor_refresh(factories, preferences, mocker):
-    preferences['federation__actor_fetch_delay'] = 0
-    actor = factories['federation.Actor']()
+    preferences["federation__actor_fetch_delay"] = 0
+    actor = factories["federation.Actor"]()
     payload = serializers.ActorSerializer(actor).data
     # actor changed their username in the meantime
-    payload['preferredUsername'] = 'New me'
+    payload["preferredUsername"] = "New me"
     get_data = mocker.patch(
-        'funkwhale_api.federation.actors.get_actor_data',
-        return_value=payload)
+        "funkwhale_api.federation.actors.get_actor_data", return_value=payload
+    )
     new_actor = actors.get_actor(actor.url)
 
     assert new_actor == actor
     assert new_actor.last_fetch_date > actor.last_fetch_date
-    assert new_actor.preferred_username == 'New me'
+    assert new_actor.preferred_username == "New me"
 
 
 def test_get_library(db, settings, mocker):
     get_key_pair = mocker.patch(
-        'funkwhale_api.federation.keys.get_key_pair',
-        return_value=(b'private', b'public'))
+        "funkwhale_api.federation.keys.get_key_pair",
+        return_value=(b"private", b"public"),
+    )
     expected = {
-        'preferred_username': 'library',
-        'domain': settings.FEDERATION_HOSTNAME,
-        'type': 'Person',
-        'name': '{}\'s library'.format(settings.FEDERATION_HOSTNAME),
-        'manually_approves_followers': True,
-        'public_key': 'public',
-        'url': utils.full_url(
-            reverse(
-                'federation:instance-actors-detail',
-                kwargs={'actor': 'library'})),
-        'shared_inbox_url': utils.full_url(
-            reverse(
-                'federation:instance-actors-inbox',
-                kwargs={'actor': 'library'})),
-        'inbox_url': utils.full_url(
-            reverse(
-                'federation:instance-actors-inbox',
-                kwargs={'actor': 'library'})),
-        'outbox_url': utils.full_url(
-            reverse(
-                'federation:instance-actors-outbox',
-                kwargs={'actor': 'library'})),
-        'summary': 'Bot account to federate with {}\'s library'.format(
-        settings.FEDERATION_HOSTNAME),
+        "preferred_username": "library",
+        "domain": settings.FEDERATION_HOSTNAME,
+        "type": "Person",
+        "name": "{}'s library".format(settings.FEDERATION_HOSTNAME),
+        "manually_approves_followers": True,
+        "public_key": "public",
+        "url": utils.full_url(
+            reverse("federation:instance-actors-detail", kwargs={"actor": "library"})
+        ),
+        "shared_inbox_url": utils.full_url(
+            reverse("federation:instance-actors-inbox", kwargs={"actor": "library"})
+        ),
+        "inbox_url": utils.full_url(
+            reverse("federation:instance-actors-inbox", kwargs={"actor": "library"})
+        ),
+        "outbox_url": utils.full_url(
+            reverse("federation:instance-actors-outbox", kwargs={"actor": "library"})
+        ),
+        "summary": "Bot account to federate with {}'s library".format(
+            settings.FEDERATION_HOSTNAME
+        ),
     }
-    actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
+    actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
     for key, value in expected.items():
         assert getattr(actor, key) == value
 
 
 def test_get_test(db, mocker, settings):
     get_key_pair = mocker.patch(
-        'funkwhale_api.federation.keys.get_key_pair',
-        return_value=(b'private', b'public'))
+        "funkwhale_api.federation.keys.get_key_pair",
+        return_value=(b"private", b"public"),
+    )
     expected = {
-        'preferred_username': 'test',
-        'domain': settings.FEDERATION_HOSTNAME,
-        'type': 'Person',
-        'name': '{}\'s test account'.format(settings.FEDERATION_HOSTNAME),
-        'manually_approves_followers': False,
-        'public_key': 'public',
-        'url': utils.full_url(
-            reverse(
-                'federation:instance-actors-detail',
-                kwargs={'actor': 'test'})),
-        'shared_inbox_url': utils.full_url(
-            reverse(
-                'federation:instance-actors-inbox',
-                kwargs={'actor': 'test'})),
-        'inbox_url': utils.full_url(
-            reverse(
-                'federation:instance-actors-inbox',
-                kwargs={'actor': 'test'})),
-        'outbox_url': utils.full_url(
-            reverse(
-                'federation:instance-actors-outbox',
-                kwargs={'actor': 'test'})),
-        'summary': 'Bot account to test federation with {}. Send me /ping and I\'ll answer you.'.format(
-        settings.FEDERATION_HOSTNAME),
+        "preferred_username": "test",
+        "domain": settings.FEDERATION_HOSTNAME,
+        "type": "Person",
+        "name": "{}'s test account".format(settings.FEDERATION_HOSTNAME),
+        "manually_approves_followers": False,
+        "public_key": "public",
+        "url": utils.full_url(
+            reverse("federation:instance-actors-detail", kwargs={"actor": "test"})
+        ),
+        "shared_inbox_url": utils.full_url(
+            reverse("federation:instance-actors-inbox", kwargs={"actor": "test"})
+        ),
+        "inbox_url": utils.full_url(
+            reverse("federation:instance-actors-inbox", kwargs={"actor": "test"})
+        ),
+        "outbox_url": utils.full_url(
+            reverse("federation:instance-actors-outbox", kwargs={"actor": "test"})
+        ),
+        "summary": "Bot account to test federation with {}. Send me /ping and I'll answer you.".format(
+            settings.FEDERATION_HOSTNAME
+        ),
     }
-    actor = actors.SYSTEM_ACTORS['test'].get_actor_instance()
+    actor = actors.SYSTEM_ACTORS["test"].get_actor_instance()
     for key, value in expected.items():
         assert getattr(actor, key) == value
 
@@ -140,233 +136,208 @@ def test_test_get_outbox():
         "@context": [
             "https://www.w3.org/ns/activitystreams",
             "https://w3id.org/security/v1",
-            {}
+            {},
         ],
         "id": utils.full_url(
-            reverse(
-                'federation:instance-actors-outbox',
-                kwargs={'actor': 'test'})),
+            reverse("federation:instance-actors-outbox", kwargs={"actor": "test"})
+        ),
         "type": "OrderedCollection",
         "totalItems": 0,
-        "orderedItems": []
+        "orderedItems": [],
     }
 
-    data = actors.SYSTEM_ACTORS['test'].get_outbox({}, actor=None)
+    data = actors.SYSTEM_ACTORS["test"].get_outbox({}, actor=None)
 
     assert data == expected
 
 
 def test_test_post_inbox_requires_authenticated_actor():
     with pytest.raises(exceptions.PermissionDenied):
-        actors.SYSTEM_ACTORS['test'].post_inbox({}, actor=None)
+        actors.SYSTEM_ACTORS["test"].post_inbox({}, actor=None)
 
 
 def test_test_post_outbox_validates_actor(nodb_factories):
-    actor = nodb_factories['federation.Actor']()
-    data = {
-        'actor': 'noop'
-    }
+    actor = nodb_factories["federation.Actor"]()
+    data = {"actor": "noop"}
     with pytest.raises(exceptions.ValidationError) as exc_info:
-        actors.SYSTEM_ACTORS['test'].post_inbox(data, actor=actor)
-        msg = 'The actor making the request do not match'
+        actors.SYSTEM_ACTORS["test"].post_inbox(data, actor=actor)
+        msg = "The actor making the request do not match"
         assert msg in exc_info.value
 
 
-def test_test_post_inbox_handles_create_note(
-        settings, mocker, factories):
-    deliver = mocker.patch(
-        'funkwhale_api.federation.activity.deliver')
-    actor = factories['federation.Actor']()
+def test_test_post_inbox_handles_create_note(settings, mocker, factories):
+    deliver = mocker.patch("funkwhale_api.federation.activity.deliver")
+    actor = factories["federation.Actor"]()
     now = timezone.now()
-    mocker.patch('django.utils.timezone.now', return_value=now)
+    mocker.patch("django.utils.timezone.now", return_value=now)
     data = {
-        'actor': actor.url,
-        'type': 'Create',
-        'id': 'http://test.federation/activity',
-        'object': {
-            'type': 'Note',
-            'id': 'http://test.federation/object',
-            'content': '<p><a>@mention</a> /ping</p>'
-        }
+        "actor": actor.url,
+        "type": "Create",
+        "id": "http://test.federation/activity",
+        "object": {
+            "type": "Note",
+            "id": "http://test.federation/object",
+            "content": "<p><a>@mention</a> /ping</p>",
+        },
     }
-    test_actor = actors.SYSTEM_ACTORS['test'].get_actor_instance()
-    expected_note = factories['federation.Note'](
-        id='https://test.federation/activities/note/{}'.format(
-            now.timestamp()
-        ),
-        content='Pong!',
+    test_actor = actors.SYSTEM_ACTORS["test"].get_actor_instance()
+    expected_note = factories["federation.Note"](
+        id="https://test.federation/activities/note/{}".format(now.timestamp()),
+        content="Pong!",
         published=now.isoformat(),
-        inReplyTo=data['object']['id'],
+        inReplyTo=data["object"]["id"],
         cc=[],
         summary=None,
         sensitive=False,
         attributedTo=test_actor.url,
         attachment=[],
         to=[actor.url],
-        url='https://{}/activities/note/{}'.format(
+        url="https://{}/activities/note/{}".format(
             settings.FEDERATION_HOSTNAME, now.timestamp()
         ),
-        tag=[{
-            'href': actor.url,
-            'name': actor.mention_username,
-            'type': 'Mention',
-        }]
+        tag=[{"href": actor.url, "name": actor.mention_username, "type": "Mention"}],
     )
     expected_activity = {
-        '@context': serializers.AP_CONTEXT,
-        'actor': test_actor.url,
-        'id': 'https://{}/activities/note/{}/activity'.format(
+        "@context": serializers.AP_CONTEXT,
+        "actor": test_actor.url,
+        "id": "https://{}/activities/note/{}/activity".format(
             settings.FEDERATION_HOSTNAME, now.timestamp()
         ),
-        'to': actor.url,
-        'type': 'Create',
-        'published': now.isoformat(),
-        'object': expected_note,
-        'cc': [],
+        "to": actor.url,
+        "type": "Create",
+        "published": now.isoformat(),
+        "object": expected_note,
+        "cc": [],
     }
-    actors.SYSTEM_ACTORS['test'].post_inbox(data, actor=actor)
+    actors.SYSTEM_ACTORS["test"].post_inbox(data, actor=actor)
     deliver.assert_called_once_with(
         expected_activity,
         to=[actor.url],
-        on_behalf_of=actors.SYSTEM_ACTORS['test'].get_actor_instance()
+        on_behalf_of=actors.SYSTEM_ACTORS["test"].get_actor_instance(),
     )
 
 
 def test_getting_actor_instance_persists_in_db(db):
-    test = actors.SYSTEM_ACTORS['test'].get_actor_instance()
+    test = actors.SYSTEM_ACTORS["test"].get_actor_instance()
     from_db = models.Actor.objects.get(url=test.url)
 
     for f in test._meta.fields:
         assert getattr(from_db, f.name) == getattr(test, f.name)
 
 
-@pytest.mark.parametrize('username,domain,expected', [
-    ('test', 'wrongdomain.com', False),
-    ('notsystem', '', False),
-    ('test', '', True),
-])
-def test_actor_is_system(
-        username, domain, expected, nodb_factories, settings):
+@pytest.mark.parametrize(
+    "username,domain,expected",
+    [("test", "wrongdomain.com", False), ("notsystem", "", False), ("test", "", True)],
+)
+def test_actor_is_system(username, domain, expected, nodb_factories, settings):
     if not domain:
         domain = settings.FEDERATION_HOSTNAME
 
-    actor = nodb_factories['federation.Actor'](
-        preferred_username=username,
-        domain=domain,
+    actor = nodb_factories["federation.Actor"](
+        preferred_username=username, domain=domain
     )
     assert actor.is_system is expected
 
 
-@pytest.mark.parametrize('username,domain,expected', [
-    ('test', 'wrongdomain.com', None),
-    ('notsystem', '', None),
-    ('test', '', actors.SYSTEM_ACTORS['test']),
-])
-def test_actor_is_system(
-        username, domain, expected, nodb_factories, settings):
+@pytest.mark.parametrize(
+    "username,domain,expected",
+    [
+        ("test", "wrongdomain.com", None),
+        ("notsystem", "", None),
+        ("test", "", actors.SYSTEM_ACTORS["test"]),
+    ],
+)
+def test_actor_is_system(username, domain, expected, nodb_factories, settings):
     if not domain:
         domain = settings.FEDERATION_HOSTNAME
-    actor = nodb_factories['federation.Actor'](
-        preferred_username=username,
-        domain=domain,
+    actor = nodb_factories["federation.Actor"](
+        preferred_username=username, domain=domain
     )
     assert actor.system_conf == expected
 
 
-@pytest.mark.parametrize('value', [False, True])
-def test_library_actor_manually_approves_based_on_preference(
-        value, preferences):
-    preferences['federation__music_needs_approval'] = value
-    library_conf = actors.SYSTEM_ACTORS['library']
+@pytest.mark.parametrize("value", [False, True])
+def test_library_actor_manually_approves_based_on_preference(value, preferences):
+    preferences["federation__music_needs_approval"] = value
+    library_conf = actors.SYSTEM_ACTORS["library"]
     assert library_conf.manually_approves_followers is value
 
 
 def test_system_actor_handle(mocker, nodb_factories):
-    handler = mocker.patch(
-        'funkwhale_api.federation.actors.TestActor.handle_create')
-    actor = nodb_factories['federation.Actor']()
-    activity = nodb_factories['federation.Activity'](
-        type='Create', actor=actor.url)
-    serializer = serializers.ActivitySerializer(
-        data=activity
-    )
+    handler = mocker.patch("funkwhale_api.federation.actors.TestActor.handle_create")
+    actor = nodb_factories["federation.Actor"]()
+    activity = nodb_factories["federation.Activity"](type="Create", actor=actor.url)
+    serializer = serializers.ActivitySerializer(data=activity)
     assert serializer.is_valid()
-    actors.SYSTEM_ACTORS['test'].handle(activity, actor)
+    actors.SYSTEM_ACTORS["test"].handle(activity, actor)
     handler.assert_called_once_with(activity, actor)
 
 
-def test_test_actor_handles_follow(
-        settings, mocker, factories):
-    deliver = mocker.patch(
-        'funkwhale_api.federation.activity.deliver')
-    actor = factories['federation.Actor']()
-    accept_follow = mocker.patch(
-        'funkwhale_api.federation.activity.accept_follow')
-    test_actor = actors.SYSTEM_ACTORS['test'].get_actor_instance()
+def test_test_actor_handles_follow(settings, mocker, factories):
+    deliver = mocker.patch("funkwhale_api.federation.activity.deliver")
+    actor = factories["federation.Actor"]()
+    accept_follow = mocker.patch("funkwhale_api.federation.activity.accept_follow")
+    test_actor = actors.SYSTEM_ACTORS["test"].get_actor_instance()
     data = {
-        'actor': actor.url,
-        'type': 'Follow',
-        'id': 'http://test.federation/user#follows/267',
-        'object': test_actor.url,
+        "actor": actor.url,
+        "type": "Follow",
+        "id": "http://test.federation/user#follows/267",
+        "object": test_actor.url,
     }
-    actors.SYSTEM_ACTORS['test'].post_inbox(data, actor=actor)
+    actors.SYSTEM_ACTORS["test"].post_inbox(data, actor=actor)
     follow = models.Follow.objects.get(target=test_actor, approved=True)
     follow_back = models.Follow.objects.get(actor=test_actor, approved=None)
     accept_follow.assert_called_once_with(follow)
     deliver.assert_called_once_with(
         serializers.FollowSerializer(follow_back).data,
         on_behalf_of=test_actor,
-        to=[actor.url]
+        to=[actor.url],
     )
 
 
-def test_test_actor_handles_undo_follow(
-        settings, mocker, factories):
-    deliver = mocker.patch(
-        'funkwhale_api.federation.activity.deliver')
-    test_actor = actors.SYSTEM_ACTORS['test'].get_actor_instance()
-    follow = factories['federation.Follow'](target=test_actor)
-    reverse_follow = factories['federation.Follow'](
-        actor=test_actor, target=follow.actor)
+def test_test_actor_handles_undo_follow(settings, mocker, factories):
+    deliver = mocker.patch("funkwhale_api.federation.activity.deliver")
+    test_actor = actors.SYSTEM_ACTORS["test"].get_actor_instance()
+    follow = factories["federation.Follow"](target=test_actor)
+    reverse_follow = factories["federation.Follow"](
+        actor=test_actor, target=follow.actor
+    )
     follow_serializer = serializers.FollowSerializer(follow)
-    reverse_follow_serializer = serializers.FollowSerializer(
-        reverse_follow)
+    reverse_follow_serializer = serializers.FollowSerializer(reverse_follow)
     undo = {
-        '@context': serializers.AP_CONTEXT,
-        'type': 'Undo',
-        'id': follow_serializer.data['id'] + '/undo',
-        'actor': follow.actor.url,
-        'object': follow_serializer.data,
+        "@context": serializers.AP_CONTEXT,
+        "type": "Undo",
+        "id": follow_serializer.data["id"] + "/undo",
+        "actor": follow.actor.url,
+        "object": follow_serializer.data,
     }
     expected_undo = {
-        '@context': serializers.AP_CONTEXT,
-        'type': 'Undo',
-        'id': reverse_follow_serializer.data['id'] + '/undo',
-        'actor': reverse_follow.actor.url,
-        'object': reverse_follow_serializer.data,
+        "@context": serializers.AP_CONTEXT,
+        "type": "Undo",
+        "id": reverse_follow_serializer.data["id"] + "/undo",
+        "actor": reverse_follow.actor.url,
+        "object": reverse_follow_serializer.data,
     }
 
-    actors.SYSTEM_ACTORS['test'].post_inbox(undo, actor=follow.actor)
+    actors.SYSTEM_ACTORS["test"].post_inbox(undo, actor=follow.actor)
     deliver.assert_called_once_with(
-        expected_undo,
-        to=[follow.actor.url],
-        on_behalf_of=test_actor,)
+        expected_undo, to=[follow.actor.url], on_behalf_of=test_actor
+    )
 
     assert models.Follow.objects.count() == 0
 
 
-def test_library_actor_handles_follow_manual_approval(
-        preferences, mocker, factories):
-    preferences['federation__music_needs_approval'] = True
-    actor = factories['federation.Actor']()
+def test_library_actor_handles_follow_manual_approval(preferences, mocker, factories):
+    preferences["federation__music_needs_approval"] = True
+    actor = factories["federation.Actor"]()
     now = timezone.now()
-    mocker.patch('django.utils.timezone.now', return_value=now)
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
+    mocker.patch("django.utils.timezone.now", return_value=now)
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
     data = {
-        'actor': actor.url,
-        'type': 'Follow',
-        'id': 'http://test.federation/user#follows/267',
-        'object': library_actor.url,
+        "actor": actor.url,
+        "type": "Follow",
+        "id": "http://test.federation/user#follows/267",
+        "object": library_actor.url,
     }
 
     library_actor.system_conf.post_inbox(data, actor=actor)
@@ -376,18 +347,16 @@ def test_library_actor_handles_follow_manual_approval(
     assert follow.approved is None
 
 
-def test_library_actor_handles_follow_auto_approval(
-        preferences, mocker, factories):
-    preferences['federation__music_needs_approval'] = False
-    actor = factories['federation.Actor']()
-    accept_follow = mocker.patch(
-        'funkwhale_api.federation.activity.accept_follow')
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
+def test_library_actor_handles_follow_auto_approval(preferences, mocker, factories):
+    preferences["federation__music_needs_approval"] = False
+    actor = factories["federation.Actor"]()
+    accept_follow = mocker.patch("funkwhale_api.federation.activity.accept_follow")
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
     data = {
-        'actor': actor.url,
-        'type': 'Follow',
-        'id': 'http://test.federation/user#follows/267',
-        'object': library_actor.url,
+        "actor": actor.url,
+        "type": "Follow",
+        "id": "http://test.federation/user#follows/267",
+        "object": library_actor.url,
     }
     library_actor.system_conf.post_inbox(data, actor=actor)
 
@@ -397,14 +366,11 @@ def test_library_actor_handles_follow_auto_approval(
     assert follow.approved is True
 
 
-def test_library_actor_handles_accept(
-        mocker, factories):
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    actor = factories['federation.Actor']()
-    pending_follow = factories['federation.Follow'](
-        actor=library_actor,
-        target=actor,
-        approved=None,
+def test_library_actor_handles_accept(mocker, factories):
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    actor = factories["federation.Actor"]()
+    pending_follow = factories["federation.Follow"](
+        actor=library_actor, target=actor, approved=None
     )
     serializer = serializers.AcceptFollowSerializer(pending_follow)
     library_actor.system_conf.post_inbox(serializer.data, actor=actor)
@@ -418,19 +384,19 @@ def test_library_actor_handle_create_audio_no_library(mocker, factories):
     # when we receive inbox create audio, we should not do anything
     # if we don't have a configured library matching the sender
     mocked_create = mocker.patch(
-        'funkwhale_api.federation.serializers.AudioSerializer.create'
+        "funkwhale_api.federation.serializers.AudioSerializer.create"
     )
-    actor = factories['federation.Actor']()
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
+    actor = factories["federation.Actor"]()
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
     data = {
-        'actor': actor.url,
-        'type': 'Create',
-        'id': 'http://test.federation/audio/create',
-        'object': {
-            'id': 'https://batch.import',
-            'type': 'Collection',
-            'totalItems': 2,
-            'items': factories['federation.Audio'].create_batch(size=2)
+        "actor": actor.url,
+        "type": "Create",
+        "id": "http://test.federation/audio/create",
+        "object": {
+            "id": "https://batch.import",
+            "type": "Collection",
+            "totalItems": 2,
+            "items": factories["federation.Audio"].create_batch(size=2),
         },
     }
     library_actor.system_conf.post_inbox(data, actor=actor)
@@ -439,26 +405,24 @@ def test_library_actor_handle_create_audio_no_library(mocker, factories):
     models.LibraryTrack.objects.count() == 0
 
 
-def test_library_actor_handle_create_audio_no_library_enabled(
-        mocker, factories):
+def test_library_actor_handle_create_audio_no_library_enabled(mocker, factories):
     # when we receive inbox create audio, we should not do anything
     # if we don't have an enabled library
     mocked_create = mocker.patch(
-        'funkwhale_api.federation.serializers.AudioSerializer.create'
+        "funkwhale_api.federation.serializers.AudioSerializer.create"
     )
-    disabled_library = factories['federation.Library'](
-        federation_enabled=False)
+    disabled_library = factories["federation.Library"](federation_enabled=False)
     actor = disabled_library.actor
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
     data = {
-        'actor': actor.url,
-        'type': 'Create',
-        'id': 'http://test.federation/audio/create',
-        'object': {
-            'id': 'https://batch.import',
-            'type': 'Collection',
-            'totalItems': 2,
-            'items': factories['federation.Audio'].create_batch(size=2)
+        "actor": actor.url,
+        "type": "Create",
+        "id": "http://test.federation/audio/create",
+        "object": {
+            "id": "https://batch.import",
+            "type": "Collection",
+            "totalItems": 2,
+            "items": factories["federation.Audio"].create_batch(size=2),
         },
     }
     library_actor.system_conf.post_inbox(data, actor=actor)
@@ -468,97 +432,91 @@ def test_library_actor_handle_create_audio_no_library_enabled(
 
 
 def test_library_actor_handle_create_audio(mocker, factories):
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    remote_library = factories['federation.Library'](
-        federation_enabled=True
-    )
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    remote_library = factories["federation.Library"](federation_enabled=True)
 
     data = {
-        'actor': remote_library.actor.url,
-        'type': 'Create',
-        'id': 'http://test.federation/audio/create',
-        'object': {
-            'id': 'https://batch.import',
-            'type': 'Collection',
-            'totalItems': 2,
-            'items': factories['federation.Audio'].create_batch(size=2)
+        "actor": remote_library.actor.url,
+        "type": "Create",
+        "id": "http://test.federation/audio/create",
+        "object": {
+            "id": "https://batch.import",
+            "type": "Collection",
+            "totalItems": 2,
+            "items": factories["federation.Audio"].create_batch(size=2),
         },
     }
 
     library_actor.system_conf.post_inbox(data, actor=remote_library.actor)
 
-    lts = list(remote_library.tracks.order_by('id'))
+    lts = list(remote_library.tracks.order_by("id"))
 
     assert len(lts) == 2
 
-    for i, a in enumerate(data['object']['items']):
+    for i, a in enumerate(data["object"]["items"]):
         lt = lts[i]
         assert lt.pk is not None
-        assert lt.url == a['id']
+        assert lt.url == a["id"]
         assert lt.library == remote_library
-        assert lt.audio_url == a['url']['href']
-        assert lt.audio_mimetype == a['url']['mediaType']
-        assert lt.metadata == a['metadata']
-        assert lt.title == a['metadata']['recording']['title']
-        assert lt.artist_name == a['metadata']['artist']['name']
-        assert lt.album_title == a['metadata']['release']['title']
-        assert lt.published_date == arrow.get(a['published'])
+        assert lt.audio_url == a["url"]["href"]
+        assert lt.audio_mimetype == a["url"]["mediaType"]
+        assert lt.metadata == a["metadata"]
+        assert lt.title == a["metadata"]["recording"]["title"]
+        assert lt.artist_name == a["metadata"]["artist"]["name"]
+        assert lt.album_title == a["metadata"]["release"]["title"]
+        assert lt.published_date == arrow.get(a["published"])
 
 
 def test_library_actor_handle_create_audio_autoimport(mocker, factories):
-    mocked_import = mocker.patch(
-        'funkwhale_api.common.utils.on_commit')
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    remote_library = factories['federation.Library'](
-        federation_enabled=True,
-        autoimport=True,
+    mocked_import = mocker.patch("funkwhale_api.common.utils.on_commit")
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    remote_library = factories["federation.Library"](
+        federation_enabled=True, autoimport=True
     )
 
     data = {
-        'actor': remote_library.actor.url,
-        'type': 'Create',
-        'id': 'http://test.federation/audio/create',
-        'object': {
-            'id': 'https://batch.import',
-            'type': 'Collection',
-            'totalItems': 2,
-            'items': factories['federation.Audio'].create_batch(size=2)
+        "actor": remote_library.actor.url,
+        "type": "Create",
+        "id": "http://test.federation/audio/create",
+        "object": {
+            "id": "https://batch.import",
+            "type": "Collection",
+            "totalItems": 2,
+            "items": factories["federation.Audio"].create_batch(size=2),
         },
     }
 
     library_actor.system_conf.post_inbox(data, actor=remote_library.actor)
 
-    lts = list(remote_library.tracks.order_by('id'))
+    lts = list(remote_library.tracks.order_by("id"))
 
     assert len(lts) == 2
 
-    for i, a in enumerate(data['object']['items']):
+    for i, a in enumerate(data["object"]["items"]):
         lt = lts[i]
         assert lt.pk is not None
-        assert lt.url == a['id']
+        assert lt.url == a["id"]
         assert lt.library == remote_library
-        assert lt.audio_url == a['url']['href']
-        assert lt.audio_mimetype == a['url']['mediaType']
-        assert lt.metadata == a['metadata']
-        assert lt.title == a['metadata']['recording']['title']
-        assert lt.artist_name == a['metadata']['artist']['name']
-        assert lt.album_title == a['metadata']['release']['title']
-        assert lt.published_date == arrow.get(a['published'])
+        assert lt.audio_url == a["url"]["href"]
+        assert lt.audio_mimetype == a["url"]["mediaType"]
+        assert lt.metadata == a["metadata"]
+        assert lt.title == a["metadata"]["recording"]["title"]
+        assert lt.artist_name == a["metadata"]["artist"]["name"]
+        assert lt.album_title == a["metadata"]["release"]["title"]
+        assert lt.published_date == arrow.get(a["published"])
 
-    batch = music_models.ImportBatch.objects.latest('id')
+    batch = music_models.ImportBatch.objects.latest("id")
 
     assert batch.jobs.count() == len(lts)
-    assert batch.source == 'federation'
+    assert batch.source == "federation"
     assert batch.submitted_by is None
 
-    for i, job in enumerate(batch.jobs.order_by('id')):
+    for i, job in enumerate(batch.jobs.order_by("id")):
         lt = lts[i]
         assert job.library_track == lt
         assert job.mbid == lt.mbid
         assert job.source == lt.url
 
         mocked_import.assert_any_call(
-            music_tasks.import_job_run.delay,
-            import_job_id=job.pk,
-            use_acoustid=False,
+            music_tasks.import_job_run.delay, import_job_id=job.pk, use_acoustid=False
         )
diff --git a/api/tests/federation/test_authentication.py b/api/tests/federation/test_authentication.py
index 2f69e4d4f1..566a3368dd 100644
--- a/api/tests/federation/test_authentication.py
+++ b/api/tests/federation/test_authentication.py
@@ -5,34 +5,31 @@ from funkwhale_api.federation import signing
 
 def test_authenticate(factories, mocker, api_request):
     private, public = keys.get_key_pair()
-    actor_url = 'https://test.federation/actor'
+    actor_url = "https://test.federation/actor"
     mocker.patch(
-        'funkwhale_api.federation.actors.get_actor_data',
+        "funkwhale_api.federation.actors.get_actor_data",
         return_value={
-            'id': actor_url,
-            'type': 'Person',
-            'outbox': 'https://test.com',
-            'inbox': 'https://test.com',
-            'preferredUsername': 'test',
-            'publicKey': {
-                'publicKeyPem': public.decode('utf-8'),
-                'owner': actor_url,
-                'id': actor_url + '#main-key',
-            }
-        })
-    signed_request = factories['federation.SignedRequest'](
-        auth__key=private,
-        auth__key_id=actor_url + '#main-key',
-        auth__headers=[
-            'date',
-        ]
+            "id": actor_url,
+            "type": "Person",
+            "outbox": "https://test.com",
+            "inbox": "https://test.com",
+            "preferredUsername": "test",
+            "publicKey": {
+                "publicKeyPem": public.decode("utf-8"),
+                "owner": actor_url,
+                "id": actor_url + "#main-key",
+            },
+        },
+    )
+    signed_request = factories["federation.SignedRequest"](
+        auth__key=private, auth__key_id=actor_url + "#main-key", auth__headers=["date"]
     )
     prepared = signed_request.prepare()
     django_request = api_request.get(
-        '/',
+        "/",
         **{
-            'HTTP_DATE': prepared.headers['date'],
-            'HTTP_SIGNATURE': prepared.headers['signature'],
+            "HTTP_DATE": prepared.headers["date"],
+            "HTTP_SIGNATURE": prepared.headers["signature"],
         }
     )
     authenticator = authentication.SignatureAuthentication()
@@ -40,5 +37,5 @@ def test_authenticate(factories, mocker, api_request):
     actor = django_request.actor
 
     assert user.is_anonymous is True
-    assert actor.public_key == public.decode('utf-8')
+    assert actor.public_key == public.decode("utf-8")
     assert actor.url == actor_url
diff --git a/api/tests/federation/test_keys.py b/api/tests/federation/test_keys.py
index 9dd71be092..0f61586805 100644
--- a/api/tests/federation/test_keys.py
+++ b/api/tests/federation/test_keys.py
@@ -3,23 +3,29 @@ import pytest
 from funkwhale_api.federation import keys
 
 
-@pytest.mark.parametrize('raw, expected', [
-    ('algorithm="test",keyId="https://test.com"', 'https://test.com'),
-    ('keyId="https://test.com",algorithm="test"', 'https://test.com'),
-])
+@pytest.mark.parametrize(
+    "raw, expected",
+    [
+        ('algorithm="test",keyId="https://test.com"', "https://test.com"),
+        ('keyId="https://test.com",algorithm="test"', "https://test.com"),
+    ],
+)
 def test_get_key_from_header(raw, expected):
     r = keys.get_key_id_from_signature_header(raw)
     assert r == expected
 
 
-@pytest.mark.parametrize('raw', [
-    'algorithm="test",keyid="badCase"',
-    'algorithm="test",wrong="wrong"',
-    'keyId = "wrong"',
-    'keyId=\'wrong\'',
-    'keyId="notanurl"',
-    'keyId="wrong://test.com"',
-])
+@pytest.mark.parametrize(
+    "raw",
+    [
+        'algorithm="test",keyid="badCase"',
+        'algorithm="test",wrong="wrong"',
+        'keyId = "wrong"',
+        "keyId='wrong'",
+        'keyId="notanurl"',
+        'keyId="wrong://test.com"',
+    ],
+)
 def test_get_key_from_header_invalid(raw):
     with pytest.raises(ValueError):
         keys.get_key_id_from_signature_header(raw)
diff --git a/api/tests/federation/test_library.py b/api/tests/federation/test_library.py
index 7a3abf5d83..8b2b185004 100644
--- a/api/tests/federation/test_library.py
+++ b/api/tests/federation/test_library.py
@@ -3,68 +3,63 @@ from funkwhale_api.federation import serializers
 
 
 def test_library_scan_from_account_name(mocker, factories):
-    actor = factories['federation.Actor'](
-        preferred_username='library',
-        domain='test.library'
+    actor = factories["federation.Actor"](
+        preferred_username="library", domain="test.library"
     )
-    get_resource_result = {'actor_url': actor.url}
+    get_resource_result = {"actor_url": actor.url}
     get_resource = mocker.patch(
-        'funkwhale_api.federation.webfinger.get_resource',
-        return_value=get_resource_result)
+        "funkwhale_api.federation.webfinger.get_resource",
+        return_value=get_resource_result,
+    )
 
     actor_data = serializers.ActorSerializer(actor).data
-    actor_data['manuallyApprovesFollowers'] = False
-    actor_data['url'] = [{
-        'type': 'Link',
-        'name': 'library',
-        'mediaType': 'application/activity+json',
-        'href': 'https://test.library'
-    }]
+    actor_data["manuallyApprovesFollowers"] = False
+    actor_data["url"] = [
+        {
+            "type": "Link",
+            "name": "library",
+            "mediaType": "application/activity+json",
+            "href": "https://test.library",
+        }
+    ]
     get_actor_data = mocker.patch(
-        'funkwhale_api.federation.actors.get_actor_data',
-        return_value=actor_data)
+        "funkwhale_api.federation.actors.get_actor_data", return_value=actor_data
+    )
 
-    get_library_data_result = {'test': 'test'}
+    get_library_data_result = {"test": "test"}
     get_library_data = mocker.patch(
-        'funkwhale_api.federation.library.get_library_data',
-        return_value=get_library_data_result)
+        "funkwhale_api.federation.library.get_library_data",
+        return_value=get_library_data_result,
+    )
 
-    result = library.scan_from_account_name('library@test.actor')
+    result = library.scan_from_account_name("library@test.actor")
 
-    get_resource.assert_called_once_with('acct:library@test.actor')
+    get_resource.assert_called_once_with("acct:library@test.actor")
     get_actor_data.assert_called_once_with(actor.url)
-    get_library_data.assert_called_once_with(actor_data['url'][0]['href'])
+    get_library_data.assert_called_once_with(actor_data["url"][0]["href"])
 
     assert result == {
-        'webfinger': get_resource_result,
-        'actor': actor_data,
-        'library': get_library_data_result,
-        'local': {
-            'following': False,
-            'awaiting_approval': False,
-        },
+        "webfinger": get_resource_result,
+        "actor": actor_data,
+        "library": get_library_data_result,
+        "local": {"following": False, "awaiting_approval": False},
     }
 
 
 def test_get_library_data(r_mock, factories):
-    actor = factories['federation.Actor']()
-    url = 'https://test.library'
-    conf = {
-        'id': url,
-        'items': [],
-        'actor': actor,
-        'page_size': 5,
-    }
+    actor = factories["federation.Actor"]()
+    url = "https://test.library"
+    conf = {"id": url, "items": [], "actor": actor, "page_size": 5}
     data = serializers.PaginatedCollectionSerializer(conf).data
     r_mock.get(url, json=data)
 
     result = library.get_library_data(url)
-    for f in ['totalItems', 'actor', 'id', 'type']:
+    for f in ["totalItems", "actor", "id", "type"]:
         assert result[f] == data[f]
 
 
 def test_get_library_data_requires_authentication(r_mock, factories):
-    url = 'https://test.library'
+    url = "https://test.library"
     r_mock.get(url, status_code=403)
     result = library.get_library_data(url)
-    assert result['errors'] == ['Permission denied while scanning library']
+    assert result["errors"] == ["Permission denied while scanning library"]
diff --git a/api/tests/federation/test_models.py b/api/tests/federation/test_models.py
index ae158e659f..be353cb670 100644
--- a/api/tests/federation/test_models.py
+++ b/api/tests/federation/test_models.py
@@ -8,34 +8,29 @@ from funkwhale_api.federation import serializers
 
 
 def test_cannot_duplicate_actor(factories):
-    actor = factories['federation.Actor']()
+    actor = factories["federation.Actor"]()
 
     with pytest.raises(db.IntegrityError):
-        factories['federation.Actor'](
-            domain=actor.domain,
-            preferred_username=actor.preferred_username,
+        factories["federation.Actor"](
+            domain=actor.domain, preferred_username=actor.preferred_username
         )
 
 
 def test_cannot_duplicate_follow(factories):
-    follow = factories['federation.Follow']()
+    follow = factories["federation.Follow"]()
 
     with pytest.raises(db.IntegrityError):
-        factories['federation.Follow'](
-            target=follow.target,
-            actor=follow.actor,
-        )
+        factories["federation.Follow"](target=follow.target, actor=follow.actor)
 
 
 def test_follow_federation_url(factories):
-    follow = factories['federation.Follow'](local=True)
-    expected = '{}#follows/{}'.format(
-        follow.actor.url, follow.uuid)
+    follow = factories["federation.Follow"](local=True)
+    expected = "{}#follows/{}".format(follow.actor.url, follow.uuid)
 
     assert follow.get_federation_url() == expected
 
 
 def test_library_model_unique_per_actor(factories):
-    library = factories['federation.Library']()
+    library = factories["federation.Library"]()
     with pytest.raises(db.IntegrityError):
-        factories['federation.Library'](actor=library.actor)
+        factories["federation.Library"](actor=library.actor)
diff --git a/api/tests/federation/test_permissions.py b/api/tests/federation/test_permissions.py
index a87f26f1b9..15990ca916 100644
--- a/api/tests/federation/test_permissions.py
+++ b/api/tests/federation/test_permissions.py
@@ -4,57 +4,59 @@ from funkwhale_api.federation import actors
 from funkwhale_api.federation import permissions
 
 
-def test_library_follower(
-        factories, api_request, anonymous_user, preferences):
-    preferences['federation__music_needs_approval'] = True
+def test_library_follower(factories, api_request, anonymous_user, preferences):
+    preferences["federation__music_needs_approval"] = True
     view = APIView.as_view()
     permission = permissions.LibraryFollower()
-    request = api_request.get('/')
-    setattr(request, 'user', anonymous_user)
+    request = api_request.get("/")
+    setattr(request, "user", anonymous_user)
     check = permission.has_permission(request, view)
 
     assert check is False
 
 
 def test_library_follower_actor_non_follower(
-        factories, api_request, anonymous_user, preferences):
-    preferences['federation__music_needs_approval'] = True
-    actor = factories['federation.Actor']()
+    factories, api_request, anonymous_user, preferences
+):
+    preferences["federation__music_needs_approval"] = True
+    actor = factories["federation.Actor"]()
     view = APIView.as_view()
     permission = permissions.LibraryFollower()
-    request = api_request.get('/')
-    setattr(request, 'user', anonymous_user)
-    setattr(request, 'actor', actor)
+    request = api_request.get("/")
+    setattr(request, "user", anonymous_user)
+    setattr(request, "actor", actor)
     check = permission.has_permission(request, view)
 
     assert check is False
 
 
 def test_library_follower_actor_follower_not_approved(
-        factories, api_request, anonymous_user, preferences):
-    preferences['federation__music_needs_approval'] = True
-    library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    follow = factories['federation.Follow'](target=library, approved=False)
+    factories, api_request, anonymous_user, preferences
+):
+    preferences["federation__music_needs_approval"] = True
+    library = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    follow = factories["federation.Follow"](target=library, approved=False)
     view = APIView.as_view()
     permission = permissions.LibraryFollower()
-    request = api_request.get('/')
-    setattr(request, 'user', anonymous_user)
-    setattr(request, 'actor', follow.actor)
+    request = api_request.get("/")
+    setattr(request, "user", anonymous_user)
+    setattr(request, "actor", follow.actor)
     check = permission.has_permission(request, view)
 
     assert check is False
 
 
 def test_library_follower_actor_follower(
-        factories, api_request, anonymous_user, preferences):
-    preferences['federation__music_needs_approval'] = True
-    library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    follow = factories['federation.Follow'](target=library, approved=True)
+    factories, api_request, anonymous_user, preferences
+):
+    preferences["federation__music_needs_approval"] = True
+    library = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    follow = factories["federation.Follow"](target=library, approved=True)
     view = APIView.as_view()
     permission = permissions.LibraryFollower()
-    request = api_request.get('/')
-    setattr(request, 'user', anonymous_user)
-    setattr(request, 'actor', follow.actor)
+    request = api_request.get("/")
+    setattr(request, "user", anonymous_user)
+    setattr(request, "actor", follow.actor)
     check = permission.has_permission(request, view)
 
     assert check is True
diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py
index fcf2ba1b67..ffcbd441df 100644
--- a/api/tests/federation/test_serializers.py
+++ b/api/tests/federation/test_serializers.py
@@ -13,25 +13,23 @@ from funkwhale_api.federation import utils
 
 def test_actor_serializer_from_ap(db):
     payload = {
-        'id': 'https://test.federation/user',
-        'type': 'Person',
-        'following': 'https://test.federation/user/following',
-        'followers': 'https://test.federation/user/followers',
-        'inbox': 'https://test.federation/user/inbox',
-        'outbox': 'https://test.federation/user/outbox',
-        'preferredUsername': 'user',
-        'name': 'Real User',
-        'summary': 'Hello world',
-        'url': 'https://test.federation/@user',
-        'manuallyApprovesFollowers': False,
-        'publicKey': {
-            'id': 'https://test.federation/user#main-key',
-            'owner': 'https://test.federation/user',
-            'publicKeyPem': 'yolo'
-        },
-        'endpoints': {
-            'sharedInbox': 'https://test.federation/inbox'
+        "id": "https://test.federation/user",
+        "type": "Person",
+        "following": "https://test.federation/user/following",
+        "followers": "https://test.federation/user/followers",
+        "inbox": "https://test.federation/user/inbox",
+        "outbox": "https://test.federation/user/outbox",
+        "preferredUsername": "user",
+        "name": "Real User",
+        "summary": "Hello world",
+        "url": "https://test.federation/@user",
+        "manuallyApprovesFollowers": False,
+        "publicKey": {
+            "id": "https://test.federation/user#main-key",
+            "owner": "https://test.federation/user",
+            "publicKeyPem": "yolo",
         },
+        "endpoints": {"sharedInbox": "https://test.federation/inbox"},
     }
 
     serializer = serializers.ActorSerializer(data=payload)
@@ -39,30 +37,30 @@ def test_actor_serializer_from_ap(db):
 
     actor = serializer.build()
 
-    assert actor.url == payload['id']
-    assert actor.inbox_url == payload['inbox']
-    assert actor.outbox_url == payload['outbox']
-    assert actor.shared_inbox_url == payload['endpoints']['sharedInbox']
-    assert actor.followers_url == payload['followers']
-    assert actor.following_url == payload['following']
-    assert actor.public_key == payload['publicKey']['publicKeyPem']
-    assert actor.preferred_username == payload['preferredUsername']
-    assert actor.name == payload['name']
-    assert actor.domain == 'test.federation'
-    assert actor.summary == payload['summary']
-    assert actor.type == 'Person'
-    assert actor.manually_approves_followers == payload['manuallyApprovesFollowers']
+    assert actor.url == payload["id"]
+    assert actor.inbox_url == payload["inbox"]
+    assert actor.outbox_url == payload["outbox"]
+    assert actor.shared_inbox_url == payload["endpoints"]["sharedInbox"]
+    assert actor.followers_url == payload["followers"]
+    assert actor.following_url == payload["following"]
+    assert actor.public_key == payload["publicKey"]["publicKeyPem"]
+    assert actor.preferred_username == payload["preferredUsername"]
+    assert actor.name == payload["name"]
+    assert actor.domain == "test.federation"
+    assert actor.summary == payload["summary"]
+    assert actor.type == "Person"
+    assert actor.manually_approves_followers == payload["manuallyApprovesFollowers"]
 
 
 def test_actor_serializer_only_mandatory_field_from_ap(db):
     payload = {
-        'id': 'https://test.federation/user',
-        'type': 'Person',
-        'following': 'https://test.federation/user/following',
-        'followers': 'https://test.federation/user/followers',
-        'inbox': 'https://test.federation/user/inbox',
-        'outbox': 'https://test.federation/user/outbox',
-        'preferredUsername': 'user',
+        "id": "https://test.federation/user",
+        "type": "Person",
+        "following": "https://test.federation/user/following",
+        "followers": "https://test.federation/user/followers",
+        "inbox": "https://test.federation/user/inbox",
+        "outbox": "https://test.federation/user/outbox",
+        "preferredUsername": "user",
     }
 
     serializer = serializers.ActorSerializer(data=payload)
@@ -70,58 +68,55 @@ def test_actor_serializer_only_mandatory_field_from_ap(db):
 
     actor = serializer.build()
 
-    assert actor.url == payload['id']
-    assert actor.inbox_url == payload['inbox']
-    assert actor.outbox_url == payload['outbox']
-    assert actor.followers_url == payload['followers']
-    assert actor.following_url == payload['following']
-    assert actor.preferred_username == payload['preferredUsername']
-    assert actor.domain == 'test.federation'
-    assert actor.type == 'Person'
+    assert actor.url == payload["id"]
+    assert actor.inbox_url == payload["inbox"]
+    assert actor.outbox_url == payload["outbox"]
+    assert actor.followers_url == payload["followers"]
+    assert actor.following_url == payload["following"]
+    assert actor.preferred_username == payload["preferredUsername"]
+    assert actor.domain == "test.federation"
+    assert actor.type == "Person"
     assert actor.manually_approves_followers is None
 
 
 def test_actor_serializer_to_ap():
     expected = {
-        '@context': [
-            'https://www.w3.org/ns/activitystreams',
-            'https://w3id.org/security/v1',
+        "@context": [
+            "https://www.w3.org/ns/activitystreams",
+            "https://w3id.org/security/v1",
             {},
         ],
-        'id': 'https://test.federation/user',
-        'type': 'Person',
-        'following': 'https://test.federation/user/following',
-        'followers': 'https://test.federation/user/followers',
-        'inbox': 'https://test.federation/user/inbox',
-        'outbox': 'https://test.federation/user/outbox',
-        'preferredUsername': 'user',
-        'name': 'Real User',
-        'summary': 'Hello world',
-        'manuallyApprovesFollowers': False,
-        'publicKey': {
-            'id': 'https://test.federation/user#main-key',
-            'owner': 'https://test.federation/user',
-            'publicKeyPem': 'yolo'
-        },
-        'endpoints': {
-            'sharedInbox': 'https://test.federation/inbox'
+        "id": "https://test.federation/user",
+        "type": "Person",
+        "following": "https://test.federation/user/following",
+        "followers": "https://test.federation/user/followers",
+        "inbox": "https://test.federation/user/inbox",
+        "outbox": "https://test.federation/user/outbox",
+        "preferredUsername": "user",
+        "name": "Real User",
+        "summary": "Hello world",
+        "manuallyApprovesFollowers": False,
+        "publicKey": {
+            "id": "https://test.federation/user#main-key",
+            "owner": "https://test.federation/user",
+            "publicKeyPem": "yolo",
         },
+        "endpoints": {"sharedInbox": "https://test.federation/inbox"},
     }
     ac = models.Actor(
-        url=expected['id'],
-        inbox_url=expected['inbox'],
-        outbox_url=expected['outbox'],
-        shared_inbox_url=expected['endpoints']['sharedInbox'],
-        followers_url=expected['followers'],
-        following_url=expected['following'],
-        public_key=expected['publicKey']['publicKeyPem'],
-        preferred_username=expected['preferredUsername'],
-        name=expected['name'],
-        domain='test.federation',
-        summary=expected['summary'],
-        type='Person',
+        url=expected["id"],
+        inbox_url=expected["inbox"],
+        outbox_url=expected["outbox"],
+        shared_inbox_url=expected["endpoints"]["sharedInbox"],
+        followers_url=expected["followers"],
+        following_url=expected["following"],
+        public_key=expected["publicKey"]["publicKeyPem"],
+        preferred_username=expected["preferredUsername"],
+        name=expected["name"],
+        domain="test.federation",
+        summary=expected["summary"],
+        type="Person",
         manually_approves_followers=False,
-
     )
     serializer = serializers.ActorSerializer(ac)
 
@@ -130,22 +125,20 @@ def test_actor_serializer_to_ap():
 
 def test_webfinger_serializer():
     expected = {
-        'subject': 'acct:service@test.federation',
-        'links': [
+        "subject": "acct:service@test.federation",
+        "links": [
             {
-                'rel': 'self',
-                'href': 'https://test.federation/federation/instance/actor',
-                'type': 'application/activity+json',
+                "rel": "self",
+                "href": "https://test.federation/federation/instance/actor",
+                "type": "application/activity+json",
             }
         ],
-        'aliases': [
-            'https://test.federation/federation/instance/actor',
-        ]
+        "aliases": ["https://test.federation/federation/instance/actor"],
     }
     actor = models.Actor(
-        url=expected['links'][0]['href'],
-        preferred_username='service',
-        domain='test.federation',
+        url=expected["links"][0]["href"],
+        preferred_username="service",
+        domain="test.federation",
     )
     serializer = serializers.ActorWebfingerSerializer(actor)
 
@@ -153,33 +146,33 @@ def test_webfinger_serializer():
 
 
 def test_follow_serializer_to_ap(factories):
-    follow = factories['federation.Follow'](local=True)
+    follow = factories["federation.Follow"](local=True)
     serializer = serializers.FollowSerializer(follow)
 
     expected = {
-        '@context': [
-            'https://www.w3.org/ns/activitystreams',
-            'https://w3id.org/security/v1',
+        "@context": [
+            "https://www.w3.org/ns/activitystreams",
+            "https://w3id.org/security/v1",
             {},
         ],
-        'id': follow.get_federation_url(),
-        'type': 'Follow',
-        'actor': follow.actor.url,
-        'object': follow.target.url,
+        "id": follow.get_federation_url(),
+        "type": "Follow",
+        "actor": follow.actor.url,
+        "object": follow.target.url,
     }
 
     assert serializer.data == expected
 
 
 def test_follow_serializer_save(factories):
-    actor = factories['federation.Actor']()
-    target = factories['federation.Actor']()
+    actor = factories["federation.Actor"]()
+    target = factories["federation.Actor"]()
 
     data = expected = {
-        'id': 'https://test.follow',
-        'type': 'Follow',
-        'actor': actor.url,
-        'object': target.url,
+        "id": "https://test.follow",
+        "type": "Follow",
+        "actor": actor.url,
+        "object": target.url,
     }
     serializer = serializers.FollowSerializer(data=data)
 
@@ -194,39 +187,39 @@ def test_follow_serializer_save(factories):
 
 
 def test_follow_serializer_save_validates_on_context(factories):
-    actor = factories['federation.Actor']()
-    target = factories['federation.Actor']()
-    impostor = factories['federation.Actor']()
+    actor = factories["federation.Actor"]()
+    target = factories["federation.Actor"]()
+    impostor = factories["federation.Actor"]()
 
     data = expected = {
-        'id': 'https://test.follow',
-        'type': 'Follow',
-        'actor': actor.url,
-        'object': target.url,
+        "id": "https://test.follow",
+        "type": "Follow",
+        "actor": actor.url,
+        "object": target.url,
     }
     serializer = serializers.FollowSerializer(
-        data=data,
-        context={'follow_actor': impostor, 'follow_target': impostor})
+        data=data, context={"follow_actor": impostor, "follow_target": impostor}
+    )
 
     assert serializer.is_valid() is False
 
-    assert 'actor' in serializer.errors
-    assert 'object' in serializer.errors
+    assert "actor" in serializer.errors
+    assert "object" in serializer.errors
 
 
 def test_accept_follow_serializer_representation(factories):
-    follow = factories['federation.Follow'](approved=None)
+    follow = factories["federation.Follow"](approved=None)
 
     expected = {
-        '@context': [
-            'https://www.w3.org/ns/activitystreams',
-            'https://w3id.org/security/v1',
+        "@context": [
+            "https://www.w3.org/ns/activitystreams",
+            "https://w3id.org/security/v1",
             {},
         ],
-        'id': follow.get_federation_url() + '/accept',
-        'type': 'Accept',
-        'actor': follow.target.url,
-        'object': serializers.FollowSerializer(follow).data,
+        "id": follow.get_federation_url() + "/accept",
+        "type": "Accept",
+        "actor": follow.target.url,
+        "object": serializers.FollowSerializer(follow).data,
     }
 
     serializer = serializers.AcceptFollowSerializer(follow)
@@ -235,18 +228,18 @@ def test_accept_follow_serializer_representation(factories):
 
 
 def test_accept_follow_serializer_save(factories):
-    follow = factories['federation.Follow'](approved=None)
+    follow = factories["federation.Follow"](approved=None)
 
     data = {
-        '@context': [
-            'https://www.w3.org/ns/activitystreams',
-            'https://w3id.org/security/v1',
+        "@context": [
+            "https://www.w3.org/ns/activitystreams",
+            "https://w3id.org/security/v1",
             {},
         ],
-        'id': follow.get_federation_url() + '/accept',
-        'type': 'Accept',
-        'actor': follow.target.url,
-        'object': serializers.FollowSerializer(follow).data,
+        "id": follow.get_federation_url() + "/accept",
+        "type": "Accept",
+        "actor": follow.target.url,
+        "object": serializers.FollowSerializer(follow).data,
     }
 
     serializer = serializers.AcceptFollowSerializer(data=data)
@@ -259,42 +252,42 @@ def test_accept_follow_serializer_save(factories):
 
 
 def test_accept_follow_serializer_validates_on_context(factories):
-    follow = factories['federation.Follow'](approved=None)
-    impostor = factories['federation.Actor']()
+    follow = factories["federation.Follow"](approved=None)
+    impostor = factories["federation.Actor"]()
     data = {
-        '@context': [
-            'https://www.w3.org/ns/activitystreams',
-            'https://w3id.org/security/v1',
+        "@context": [
+            "https://www.w3.org/ns/activitystreams",
+            "https://w3id.org/security/v1",
             {},
         ],
-        'id': follow.get_federation_url() + '/accept',
-        'type': 'Accept',
-        'actor': impostor.url,
-        'object': serializers.FollowSerializer(follow).data,
+        "id": follow.get_federation_url() + "/accept",
+        "type": "Accept",
+        "actor": impostor.url,
+        "object": serializers.FollowSerializer(follow).data,
     }
 
     serializer = serializers.AcceptFollowSerializer(
-        data=data,
-        context={'follow_actor': impostor, 'follow_target': impostor})
+        data=data, context={"follow_actor": impostor, "follow_target": impostor}
+    )
 
     assert serializer.is_valid() is False
-    assert 'actor' in serializer.errors['object']
-    assert 'object' in serializer.errors['object']
+    assert "actor" in serializer.errors["object"]
+    assert "object" in serializer.errors["object"]
 
 
 def test_undo_follow_serializer_representation(factories):
-    follow = factories['federation.Follow'](approved=True)
+    follow = factories["federation.Follow"](approved=True)
 
     expected = {
-        '@context': [
-            'https://www.w3.org/ns/activitystreams',
-            'https://w3id.org/security/v1',
+        "@context": [
+            "https://www.w3.org/ns/activitystreams",
+            "https://w3id.org/security/v1",
             {},
         ],
-        'id': follow.get_federation_url() + '/undo',
-        'type': 'Undo',
-        'actor': follow.actor.url,
-        'object': serializers.FollowSerializer(follow).data,
+        "id": follow.get_federation_url() + "/undo",
+        "type": "Undo",
+        "actor": follow.actor.url,
+        "object": serializers.FollowSerializer(follow).data,
     }
 
     serializer = serializers.UndoFollowSerializer(follow)
@@ -303,18 +296,18 @@ def test_undo_follow_serializer_representation(factories):
 
 
 def test_undo_follow_serializer_save(factories):
-    follow = factories['federation.Follow'](approved=True)
+    follow = factories["federation.Follow"](approved=True)
 
     data = {
-        '@context': [
-            'https://www.w3.org/ns/activitystreams',
-            'https://w3id.org/security/v1',
+        "@context": [
+            "https://www.w3.org/ns/activitystreams",
+            "https://w3id.org/security/v1",
             {},
         ],
-        'id': follow.get_federation_url() + '/undo',
-        'type': 'Undo',
-        'actor': follow.actor.url,
-        'object': serializers.FollowSerializer(follow).data,
+        "id": follow.get_federation_url() + "/undo",
+        "type": "Undo",
+        "actor": follow.actor.url,
+        "object": serializers.FollowSerializer(follow).data,
     }
 
     serializer = serializers.UndoFollowSerializer(data=data)
@@ -326,53 +319,53 @@ def test_undo_follow_serializer_save(factories):
 
 
 def test_undo_follow_serializer_validates_on_context(factories):
-    follow = factories['federation.Follow'](approved=True)
-    impostor = factories['federation.Actor']()
+    follow = factories["federation.Follow"](approved=True)
+    impostor = factories["federation.Actor"]()
     data = {
-        '@context': [
-            'https://www.w3.org/ns/activitystreams',
-            'https://w3id.org/security/v1',
+        "@context": [
+            "https://www.w3.org/ns/activitystreams",
+            "https://w3id.org/security/v1",
             {},
         ],
-        'id': follow.get_federation_url() + '/undo',
-        'type': 'Undo',
-        'actor': impostor.url,
-        'object': serializers.FollowSerializer(follow).data,
+        "id": follow.get_federation_url() + "/undo",
+        "type": "Undo",
+        "actor": impostor.url,
+        "object": serializers.FollowSerializer(follow).data,
     }
 
     serializer = serializers.UndoFollowSerializer(
-        data=data,
-        context={'follow_actor': impostor, 'follow_target': impostor})
+        data=data, context={"follow_actor": impostor, "follow_target": impostor}
+    )
 
     assert serializer.is_valid() is False
-    assert 'actor' in serializer.errors['object']
-    assert 'object' in serializer.errors['object']
+    assert "actor" in serializer.errors["object"]
+    assert "object" in serializer.errors["object"]
 
 
 def test_paginated_collection_serializer(factories):
-    tfs = factories['music.TrackFile'].create_batch(size=5)
-    actor = factories['federation.Actor'](local=True)
+    tfs = factories["music.TrackFile"].create_batch(size=5)
+    actor = factories["federation.Actor"](local=True)
 
     conf = {
-        'id': 'https://test.federation/test',
-        'items': tfs,
-        'item_serializer': serializers.AudioSerializer,
-        'actor': actor,
-        'page_size': 2,
+        "id": "https://test.federation/test",
+        "items": tfs,
+        "item_serializer": serializers.AudioSerializer,
+        "actor": actor,
+        "page_size": 2,
     }
     expected = {
-        '@context': [
-            'https://www.w3.org/ns/activitystreams',
-            'https://w3id.org/security/v1',
+        "@context": [
+            "https://www.w3.org/ns/activitystreams",
+            "https://w3id.org/security/v1",
             {},
         ],
-        'type': 'Collection',
-        'id': conf['id'],
-        'actor': actor.url,
-        'totalItems': len(tfs),
-        'current': conf['id'] + '?page=1',
-        'last': conf['id'] + '?page=3',
-        'first': conf['id'] + '?page=1',
+        "type": "Collection",
+        "id": conf["id"],
+        "actor": actor.url,
+        "totalItems": len(tfs),
+        "current": conf["id"] + "?page=1",
+        "last": conf["id"] + "?page=3",
+        "first": conf["id"] + "?page=1",
     }
 
     serializer = serializers.PaginatedCollectionSerializer(conf)
@@ -382,108 +375,102 @@ def test_paginated_collection_serializer(factories):
 
 def test_paginated_collection_serializer_validation():
     data = {
-        'type': 'Collection',
-        'id': 'https://test.federation/test',
-        'totalItems': 5,
-        'actor': 'http://test.actor',
-        'first': 'https://test.federation/test?page=1',
-        'last': 'https://test.federation/test?page=1',
-        'items': []
+        "type": "Collection",
+        "id": "https://test.federation/test",
+        "totalItems": 5,
+        "actor": "http://test.actor",
+        "first": "https://test.federation/test?page=1",
+        "last": "https://test.federation/test?page=1",
+        "items": [],
     }
 
-    serializer = serializers.PaginatedCollectionSerializer(
-        data=data
-    )
+    serializer = serializers.PaginatedCollectionSerializer(data=data)
 
     assert serializer.is_valid(raise_exception=True) is True
-    assert serializer.validated_data['totalItems'] == 5
-    assert serializer.validated_data['id'] == data['id']
-    assert serializer.validated_data['actor'] == data['actor']
+    assert serializer.validated_data["totalItems"] == 5
+    assert serializer.validated_data["id"] == data["id"]
+    assert serializer.validated_data["actor"] == data["actor"]
 
 
 def test_collection_page_serializer_validation():
-    base = 'https://test.federation/test'
+    base = "https://test.federation/test"
     data = {
-        'type': 'CollectionPage',
-        'id': base + '?page=2',
-        'totalItems': 5,
-        'actor': 'https://test.actor',
-        'items': [],
-        'first': 'https://test.federation/test?page=1',
-        'last': 'https://test.federation/test?page=3',
-        'prev': base + '?page=1',
-        'next': base + '?page=3',
-        'partOf': base,
+        "type": "CollectionPage",
+        "id": base + "?page=2",
+        "totalItems": 5,
+        "actor": "https://test.actor",
+        "items": [],
+        "first": "https://test.federation/test?page=1",
+        "last": "https://test.federation/test?page=3",
+        "prev": base + "?page=1",
+        "next": base + "?page=3",
+        "partOf": base,
     }
 
-    serializer = serializers.CollectionPageSerializer(
-        data=data
-    )
+    serializer = serializers.CollectionPageSerializer(data=data)
 
     assert serializer.is_valid(raise_exception=True) is True
-    assert serializer.validated_data['totalItems'] == 5
-    assert serializer.validated_data['id'] == data['id']
-    assert serializer.validated_data['actor'] == data['actor']
-    assert serializer.validated_data['items'] == []
-    assert serializer.validated_data['prev'] == data['prev']
-    assert serializer.validated_data['next'] == data['next']
-    assert serializer.validated_data['partOf'] == data['partOf']
+    assert serializer.validated_data["totalItems"] == 5
+    assert serializer.validated_data["id"] == data["id"]
+    assert serializer.validated_data["actor"] == data["actor"]
+    assert serializer.validated_data["items"] == []
+    assert serializer.validated_data["prev"] == data["prev"]
+    assert serializer.validated_data["next"] == data["next"]
+    assert serializer.validated_data["partOf"] == data["partOf"]
 
 
 def test_collection_page_serializer_can_validate_child():
     data = {
-        'type': 'CollectionPage',
-        'id': 'https://test.page?page=2',
-        'actor': 'https://test.actor',
-        'first': 'https://test.page?page=1',
-        'last': 'https://test.page?page=3',
-        'partOf': 'https://test.page',
-        'totalItems': 1,
-        'items': [{'in': 'valid'}],
+        "type": "CollectionPage",
+        "id": "https://test.page?page=2",
+        "actor": "https://test.actor",
+        "first": "https://test.page?page=1",
+        "last": "https://test.page?page=3",
+        "partOf": "https://test.page",
+        "totalItems": 1,
+        "items": [{"in": "valid"}],
     }
 
     serializer = serializers.CollectionPageSerializer(
-        data=data,
-        context={'item_serializer': serializers.AudioSerializer}
+        data=data, context={"item_serializer": serializers.AudioSerializer}
     )
 
     # child are validated but not included in data if not valid
     assert serializer.is_valid(raise_exception=True) is True
-    assert len(serializer.validated_data['items']) == 0
+    assert len(serializer.validated_data["items"]) == 0
 
 
 def test_collection_page_serializer(factories):
-    tfs = factories['music.TrackFile'].create_batch(size=5)
-    actor = factories['federation.Actor'](local=True)
+    tfs = factories["music.TrackFile"].create_batch(size=5)
+    actor = factories["federation.Actor"](local=True)
 
     conf = {
-        'id': 'https://test.federation/test',
-        'item_serializer': serializers.AudioSerializer,
-        'actor': actor,
-        'page': Paginator(tfs, 2).page(2),
+        "id": "https://test.federation/test",
+        "item_serializer": serializers.AudioSerializer,
+        "actor": actor,
+        "page": Paginator(tfs, 2).page(2),
     }
     expected = {
-        '@context': [
-            'https://www.w3.org/ns/activitystreams',
-            'https://w3id.org/security/v1',
+        "@context": [
+            "https://www.w3.org/ns/activitystreams",
+            "https://w3id.org/security/v1",
             {},
         ],
-        'type': 'CollectionPage',
-        'id': conf['id'] + '?page=2',
-        'actor': actor.url,
-        'totalItems': len(tfs),
-        'partOf': conf['id'],
-        'prev': conf['id'] + '?page=1',
-        'next': conf['id'] + '?page=3',
-        'first': conf['id'] + '?page=1',
-        'last': conf['id'] + '?page=3',
-        'items': [
-            conf['item_serializer'](
-                i,
-                context={'actor': actor, 'include_ap_context': False}
+        "type": "CollectionPage",
+        "id": conf["id"] + "?page=2",
+        "actor": actor.url,
+        "totalItems": len(tfs),
+        "partOf": conf["id"],
+        "prev": conf["id"] + "?page=1",
+        "next": conf["id"] + "?page=3",
+        "first": conf["id"] + "?page=1",
+        "last": conf["id"] + "?page=3",
+        "items": [
+            conf["item_serializer"](
+                i, context={"actor": actor, "include_ap_context": False}
             ).data
-            for i in conf['page'].object_list
-        ]
+            for i in conf["page"].object_list
+        ],
     }
 
     serializer = serializers.CollectionPageSerializer(conf)
@@ -492,35 +479,37 @@ def test_collection_page_serializer(factories):
 
 
 def test_activity_pub_audio_serializer_to_library_track(factories):
-    remote_library = factories['federation.Library']()
-    audio = factories['federation.Audio']()
+    remote_library = factories["federation.Library"]()
+    audio = factories["federation.Audio"]()
     serializer = serializers.AudioSerializer(
-        data=audio, context={'library': remote_library})
+        data=audio, context={"library": remote_library}
+    )
 
     assert serializer.is_valid(raise_exception=True)
 
     lt = serializer.save()
 
     assert lt.pk is not None
-    assert lt.url == audio['id']
+    assert lt.url == audio["id"]
     assert lt.library == remote_library
-    assert lt.audio_url == audio['url']['href']
-    assert lt.audio_mimetype == audio['url']['mediaType']
-    assert lt.metadata == audio['metadata']
-    assert lt.title == audio['metadata']['recording']['title']
-    assert lt.artist_name == audio['metadata']['artist']['name']
-    assert lt.album_title == audio['metadata']['release']['title']
-    assert lt.published_date == arrow.get(audio['published'])
-
-
-def test_activity_pub_audio_serializer_to_library_track_no_duplicate(
-        factories):
-    remote_library = factories['federation.Library']()
-    audio = factories['federation.Audio']()
+    assert lt.audio_url == audio["url"]["href"]
+    assert lt.audio_mimetype == audio["url"]["mediaType"]
+    assert lt.metadata == audio["metadata"]
+    assert lt.title == audio["metadata"]["recording"]["title"]
+    assert lt.artist_name == audio["metadata"]["artist"]["name"]
+    assert lt.album_title == audio["metadata"]["release"]["title"]
+    assert lt.published_date == arrow.get(audio["published"])
+
+
+def test_activity_pub_audio_serializer_to_library_track_no_duplicate(factories):
+    remote_library = factories["federation.Library"]()
+    audio = factories["federation.Audio"]()
     serializer1 = serializers.AudioSerializer(
-        data=audio, context={'library': remote_library})
+        data=audio, context={"library": remote_library}
+    )
     serializer2 = serializers.AudioSerializer(
-        data=audio, context={'library': remote_library})
+        data=audio, context={"library": remote_library}
+    )
 
     assert serializer1.is_valid() is True
     assert serializer2.is_valid() is True
@@ -533,192 +522,168 @@ def test_activity_pub_audio_serializer_to_library_track_no_duplicate(
 
 
 def test_activity_pub_audio_serializer_to_ap(factories):
-    tf = factories['music.TrackFile'](
-        mimetype='audio/mp3',
-        bitrate=42,
-        duration=43,
-        size=44,
+    tf = factories["music.TrackFile"](
+        mimetype="audio/mp3", bitrate=42, duration=43, size=44
     )
-    library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
+    library = actors.SYSTEM_ACTORS["library"].get_actor_instance()
     expected = {
-        '@context': serializers.AP_CONTEXT,
-        'type': 'Audio',
-        'id': tf.get_federation_url(),
-        'name': tf.track.full_name,
-        'published': tf.creation_date.isoformat(),
-        'updated': tf.modification_date.isoformat(),
-        'metadata': {
-            'artist': {
-                'musicbrainz_id': tf.track.artist.mbid,
-                'name': tf.track.artist.name,
+        "@context": serializers.AP_CONTEXT,
+        "type": "Audio",
+        "id": tf.get_federation_url(),
+        "name": tf.track.full_name,
+        "published": tf.creation_date.isoformat(),
+        "updated": tf.modification_date.isoformat(),
+        "metadata": {
+            "artist": {
+                "musicbrainz_id": tf.track.artist.mbid,
+                "name": tf.track.artist.name,
             },
-            'release': {
-                'musicbrainz_id': tf.track.album.mbid,
-                'title': tf.track.album.title,
+            "release": {
+                "musicbrainz_id": tf.track.album.mbid,
+                "title": tf.track.album.title,
             },
-            'recording': {
-                'musicbrainz_id': tf.track.mbid,
-                'title': tf.track.title,
-            },
-            'size': tf.size,
-            'length': tf.duration,
-            'bitrate': tf.bitrate,
+            "recording": {"musicbrainz_id": tf.track.mbid, "title": tf.track.title},
+            "size": tf.size,
+            "length": tf.duration,
+            "bitrate": tf.bitrate,
         },
-        'url': {
-            'href': utils.full_url(tf.path),
-            'type': 'Link',
-            'mediaType': 'audio/mp3'
+        "url": {
+            "href": utils.full_url(tf.path),
+            "type": "Link",
+            "mediaType": "audio/mp3",
         },
-        'attributedTo': [
-            library.url
-        ]
+        "attributedTo": [library.url],
     }
 
-    serializer = serializers.AudioSerializer(tf, context={'actor': library})
+    serializer = serializers.AudioSerializer(tf, context={"actor": library})
 
     assert serializer.data == expected
 
 
 def test_activity_pub_audio_serializer_to_ap_no_mbid(factories):
-    tf = factories['music.TrackFile'](
-        mimetype='audio/mp3',
+    tf = factories["music.TrackFile"](
+        mimetype="audio/mp3",
         track__mbid=None,
         track__album__mbid=None,
         track__album__artist__mbid=None,
     )
-    library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
+    library = actors.SYSTEM_ACTORS["library"].get_actor_instance()
     expected = {
-        '@context': serializers.AP_CONTEXT,
-        'type': 'Audio',
-        'id': tf.get_federation_url(),
-        'name': tf.track.full_name,
-        'published': tf.creation_date.isoformat(),
-        'updated': tf.modification_date.isoformat(),
-        'metadata': {
-            'artist': {
-                'name': tf.track.artist.name,
-                'musicbrainz_id': None,
-            },
-            'release': {
-                'title': tf.track.album.title,
-                'musicbrainz_id': None,
-            },
-            'recording': {
-                'title': tf.track.title,
-                'musicbrainz_id': None,
-            },
-            'size': None,
-            'length': None,
-            'bitrate': None,
+        "@context": serializers.AP_CONTEXT,
+        "type": "Audio",
+        "id": tf.get_federation_url(),
+        "name": tf.track.full_name,
+        "published": tf.creation_date.isoformat(),
+        "updated": tf.modification_date.isoformat(),
+        "metadata": {
+            "artist": {"name": tf.track.artist.name, "musicbrainz_id": None},
+            "release": {"title": tf.track.album.title, "musicbrainz_id": None},
+            "recording": {"title": tf.track.title, "musicbrainz_id": None},
+            "size": None,
+            "length": None,
+            "bitrate": None,
         },
-        'url': {
-            'href': utils.full_url(tf.path),
-            'type': 'Link',
-            'mediaType': 'audio/mp3'
+        "url": {
+            "href": utils.full_url(tf.path),
+            "type": "Link",
+            "mediaType": "audio/mp3",
         },
-        'attributedTo': [
-            library.url
-        ]
+        "attributedTo": [library.url],
     }
 
-    serializer = serializers.AudioSerializer(tf, context={'actor': library})
+    serializer = serializers.AudioSerializer(tf, context={"actor": library})
 
     assert serializer.data == expected
 
 
 def test_collection_serializer_to_ap(factories):
-    tf1 = factories['music.TrackFile'](mimetype='audio/mp3')
-    tf2 = factories['music.TrackFile'](mimetype='audio/ogg')
-    library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
+    tf1 = factories["music.TrackFile"](mimetype="audio/mp3")
+    tf2 = factories["music.TrackFile"](mimetype="audio/ogg")
+    library = actors.SYSTEM_ACTORS["library"].get_actor_instance()
     expected = {
-        '@context': serializers.AP_CONTEXT,
-        'id': 'https://test.id',
-        'actor': library.url,
-        'totalItems': 2,
-        'type': 'Collection',
-        'items': [
+        "@context": serializers.AP_CONTEXT,
+        "id": "https://test.id",
+        "actor": library.url,
+        "totalItems": 2,
+        "type": "Collection",
+        "items": [
             serializers.AudioSerializer(
-                tf1, context={'actor': library, 'include_ap_context': False}
+                tf1, context={"actor": library, "include_ap_context": False}
             ).data,
             serializers.AudioSerializer(
-                tf2, context={'actor': library, 'include_ap_context': False}
+                tf2, context={"actor": library, "include_ap_context": False}
             ).data,
-        ]
+        ],
     }
 
     collection = {
-        'id': expected['id'],
-        'actor': library,
-        'items': [tf1, tf2],
-        'item_serializer': serializers.AudioSerializer
+        "id": expected["id"],
+        "actor": library,
+        "items": [tf1, tf2],
+        "item_serializer": serializers.AudioSerializer,
     }
     serializer = serializers.CollectionSerializer(
-        collection, context={'actor': library, 'id': 'https://test.id'})
+        collection, context={"actor": library, "id": "https://test.id"}
+    )
 
     assert serializer.data == expected
 
 
 def test_api_library_create_serializer_save(factories, r_mock):
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    actor = factories['federation.Actor']()
-    follow = factories['federation.Follow'](
-        target=actor,
-        actor=library_actor,
-    )
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    actor = factories["federation.Actor"]()
+    follow = factories["federation.Follow"](target=actor, actor=library_actor)
     actor_data = serializers.ActorSerializer(actor).data
-    actor_data['url'] = [{
-        'href': 'https://test.library',
-        'name': 'library',
-        'type': 'Link',
-    }]
+    actor_data["url"] = [
+        {"href": "https://test.library", "name": "library", "type": "Link"}
+    ]
     library_conf = {
-        'id': 'https://test.library',
-        'items': range(10),
-        'actor': actor,
-        'page_size': 5,
+        "id": "https://test.library",
+        "items": range(10),
+        "actor": actor,
+        "page_size": 5,
     }
     library_data = serializers.PaginatedCollectionSerializer(library_conf).data
     r_mock.get(actor.url, json=actor_data)
-    r_mock.get('https://test.library', json=library_data)
+    r_mock.get("https://test.library", json=library_data)
     data = {
-        'actor': actor.url,
-        'autoimport': False,
-        'federation_enabled': True,
-        'download_files': False,
+        "actor": actor.url,
+        "autoimport": False,
+        "federation_enabled": True,
+        "download_files": False,
     }
 
     serializer = serializers.APILibraryCreateSerializer(data=data)
     assert serializer.is_valid(raise_exception=True) is True
     library = serializer.save()
-    follow = models.Follow.objects.get(
-        target=actor, actor=library_actor, approved=None)
+    follow = models.Follow.objects.get(target=actor, actor=library_actor, approved=None)
 
-    assert library.autoimport is data['autoimport']
-    assert library.federation_enabled is data['federation_enabled']
-    assert library.download_files is data['download_files']
+    assert library.autoimport is data["autoimport"]
+    assert library.federation_enabled is data["federation_enabled"]
+    assert library.download_files is data["download_files"]
     assert library.tracks_count == 10
     assert library.actor == actor
     assert library.follow == follow
 
 
 def test_tapi_library_track_serializer_not_imported(factories):
-    lt = factories['federation.LibraryTrack']()
+    lt = factories["federation.LibraryTrack"]()
     serializer = serializers.APILibraryTrackSerializer(lt)
 
-    assert serializer.get_status(lt) == 'not_imported'
+    assert serializer.get_status(lt) == "not_imported"
 
 
 def test_tapi_library_track_serializer_imported(factories):
-    tf = factories['music.TrackFile'](federation=True)
+    tf = factories["music.TrackFile"](federation=True)
     lt = tf.library_track
     serializer = serializers.APILibraryTrackSerializer(lt)
 
-    assert serializer.get_status(lt) == 'imported'
+    assert serializer.get_status(lt) == "imported"
 
 
 def test_tapi_library_track_serializer_import_pending(factories):
-    job = factories['music.ImportJob'](federation=True, status='pending')
+    job = factories["music.ImportJob"](federation=True, status="pending")
     lt = job.library_track
     serializer = serializers.APILibraryTrackSerializer(lt)
 
-    assert serializer.get_status(lt) == 'import_pending'
+    assert serializer.get_status(lt) == "import_pending"
diff --git a/api/tests/federation/test_signing.py b/api/tests/federation/test_signing.py
index 0c1ec2e0ba..e88425e8f3 100644
--- a/api/tests/federation/test_signing.py
+++ b/api/tests/federation/test_signing.py
@@ -8,36 +8,31 @@ from funkwhale_api.federation import keys
 
 
 def test_can_sign_and_verify_request(nodb_factories):
-    private, public = nodb_factories['federation.KeyPair']()
-    auth = nodb_factories['federation.SignatureAuth'](key=private)
-    request = nodb_factories['federation.SignedRequest'](
-        auth=auth
-    )
+    private, public = nodb_factories["federation.KeyPair"]()
+    auth = nodb_factories["federation.SignatureAuth"](key=private)
+    request = nodb_factories["federation.SignedRequest"](auth=auth)
     prepared_request = request.prepare()
-    assert 'date' in prepared_request.headers
-    assert 'signature' in prepared_request.headers
-    assert signing.verify(
-        prepared_request, public) is None
+    assert "date" in prepared_request.headers
+    assert "signature" in prepared_request.headers
+    assert signing.verify(prepared_request, public) is None
 
 
 def test_can_sign_and_verify_request_digest(nodb_factories):
-    private, public = nodb_factories['federation.KeyPair']()
-    auth = nodb_factories['federation.SignatureAuth'](key=private)
-    request = nodb_factories['federation.SignedRequest'](
-        auth=auth,
-        method='post',
-        data=b'hello=world'
+    private, public = nodb_factories["federation.KeyPair"]()
+    auth = nodb_factories["federation.SignatureAuth"](key=private)
+    request = nodb_factories["federation.SignedRequest"](
+        auth=auth, method="post", data=b"hello=world"
     )
     prepared_request = request.prepare()
-    assert 'date' in prepared_request.headers
-    assert 'digest' in prepared_request.headers
-    assert 'signature' in prepared_request.headers
+    assert "date" in prepared_request.headers
+    assert "digest" in prepared_request.headers
+    assert "signature" in prepared_request.headers
     assert signing.verify(prepared_request, public) is None
 
 
 def test_verify_fails_with_wrong_key(nodb_factories):
-    wrong_private, wrong_public = nodb_factories['federation.KeyPair']()
-    request = nodb_factories['federation.SignedRequest']()
+    wrong_private, wrong_public = nodb_factories["federation.KeyPair"]()
+    request = nodb_factories["federation.SignedRequest"]()
     prepared_request = request.prepare()
 
     with pytest.raises(cryptography.exceptions.InvalidSignature):
@@ -46,18 +41,15 @@ def test_verify_fails_with_wrong_key(nodb_factories):
 
 def test_can_verify_django_request(factories, fake_request):
     private_key, public_key = keys.get_key_pair()
-    signed_request = factories['federation.SignedRequest'](
-        auth__key=private_key,
-        auth__headers=[
-            'date',
-        ]
+    signed_request = factories["federation.SignedRequest"](
+        auth__key=private_key, auth__headers=["date"]
     )
     prepared = signed_request.prepare()
     django_request = fake_request.get(
-        '/',
+        "/",
         **{
-            'HTTP_DATE': prepared.headers['date'],
-            'HTTP_SIGNATURE': prepared.headers['signature'],
+            "HTTP_DATE": prepared.headers["date"],
+            "HTTP_SIGNATURE": prepared.headers["signature"],
         }
     )
     assert signing.verify_django(django_request, public_key) is None
@@ -65,22 +57,19 @@ def test_can_verify_django_request(factories, fake_request):
 
 def test_can_verify_django_request_digest(factories, fake_request):
     private_key, public_key = keys.get_key_pair()
-    signed_request = factories['federation.SignedRequest'](
+    signed_request = factories["federation.SignedRequest"](
         auth__key=private_key,
-        method='post',
-        data=b'hello=world',
-        auth__headers=[
-            'date',
-            'digest',
-        ]
+        method="post",
+        data=b"hello=world",
+        auth__headers=["date", "digest"],
     )
     prepared = signed_request.prepare()
     django_request = fake_request.post(
-        '/',
+        "/",
         **{
-            'HTTP_DATE': prepared.headers['date'],
-            'HTTP_DIGEST': prepared.headers['digest'],
-            'HTTP_SIGNATURE': prepared.headers['signature'],
+            "HTTP_DATE": prepared.headers["date"],
+            "HTTP_DIGEST": prepared.headers["digest"],
+            "HTTP_SIGNATURE": prepared.headers["signature"],
         }
     )
 
@@ -89,22 +78,19 @@ def test_can_verify_django_request_digest(factories, fake_request):
 
 def test_can_verify_django_request_digest_failure(factories, fake_request):
     private_key, public_key = keys.get_key_pair()
-    signed_request = factories['federation.SignedRequest'](
+    signed_request = factories["federation.SignedRequest"](
         auth__key=private_key,
-        method='post',
-        data=b'hello=world',
-        auth__headers=[
-            'date',
-            'digest',
-        ]
+        method="post",
+        data=b"hello=world",
+        auth__headers=["date", "digest"],
     )
     prepared = signed_request.prepare()
     django_request = fake_request.post(
-        '/',
+        "/",
         **{
-            'HTTP_DATE': prepared.headers['date'],
-            'HTTP_DIGEST': prepared.headers['digest'] + 'noop',
-            'HTTP_SIGNATURE': prepared.headers['signature'],
+            "HTTP_DATE": prepared.headers["date"],
+            "HTTP_DIGEST": prepared.headers["digest"] + "noop",
+            "HTTP_SIGNATURE": prepared.headers["signature"],
         }
     )
 
@@ -114,19 +100,12 @@ def test_can_verify_django_request_digest_failure(factories, fake_request):
 
 def test_can_verify_django_request_failure(factories, fake_request):
     private_key, public_key = keys.get_key_pair()
-    signed_request = factories['federation.SignedRequest'](
-        auth__key=private_key,
-        auth__headers=[
-            'date',
-        ]
+    signed_request = factories["federation.SignedRequest"](
+        auth__key=private_key, auth__headers=["date"]
     )
     prepared = signed_request.prepare()
     django_request = fake_request.get(
-        '/',
-        **{
-            'HTTP_DATE': 'Wrong',
-            'HTTP_SIGNATURE': prepared.headers['signature'],
-        }
+        "/", **{"HTTP_DATE": "Wrong", "HTTP_SIGNATURE": prepared.headers["signature"]}
     )
     with pytest.raises(cryptography.exceptions.InvalidSignature):
         signing.verify_django(django_request, public_key)
diff --git a/api/tests/federation/test_tasks.py b/api/tests/federation/test_tasks.py
index 3517e8feb2..f6fafffd60 100644
--- a/api/tests/federation/test_tasks.py
+++ b/api/tests/federation/test_tasks.py
@@ -11,122 +11,111 @@ from funkwhale_api.federation import tasks
 
 
 def test_scan_library_does_nothing_if_federation_disabled(mocker, factories):
-    library = factories['federation.Library'](federation_enabled=False)
+    library = factories["federation.Library"](federation_enabled=False)
     tasks.scan_library(library_id=library.pk)
 
     assert library.tracks.count() == 0
 
 
-def test_scan_library_page_does_nothing_if_federation_disabled(
-        mocker, factories):
-    library = factories['federation.Library'](federation_enabled=False)
+def test_scan_library_page_does_nothing_if_federation_disabled(mocker, factories):
+    library = factories["federation.Library"](federation_enabled=False)
     tasks.scan_library_page(library_id=library.pk, page_url=None)
 
     assert library.tracks.count() == 0
 
 
-def test_scan_library_fetches_page_and_calls_scan_page(
-        mocker, factories, r_mock):
+def test_scan_library_fetches_page_and_calls_scan_page(mocker, factories, r_mock):
     now = timezone.now()
-    library = factories['federation.Library'](federation_enabled=True)
+    library = factories["federation.Library"](federation_enabled=True)
     collection_conf = {
-        'actor': library.actor,
-        'id': library.url,
-        'page_size': 10,
-        'items': range(10),
+        "actor": library.actor,
+        "id": library.url,
+        "page_size": 10,
+        "items": range(10),
     }
     collection = serializers.PaginatedCollectionSerializer(collection_conf)
-    scan_page = mocker.patch(
-        'funkwhale_api.federation.tasks.scan_library_page.delay')
-    r_mock.get(collection_conf['id'], json=collection.data)
+    scan_page = mocker.patch("funkwhale_api.federation.tasks.scan_library_page.delay")
+    r_mock.get(collection_conf["id"], json=collection.data)
     tasks.scan_library(library_id=library.pk)
 
     scan_page.assert_called_once_with(
-        library_id=library.id,
-        page_url=collection.data['first'],
-        until=None,
+        library_id=library.id, page_url=collection.data["first"], until=None
     )
     library.refresh_from_db()
     assert library.fetched_date > now
 
 
-def test_scan_page_fetches_page_and_creates_tracks(
-        mocker, factories, r_mock):
-    library = factories['federation.Library'](federation_enabled=True)
-    tfs = factories['music.TrackFile'].create_batch(size=5)
+def test_scan_page_fetches_page_and_creates_tracks(mocker, factories, r_mock):
+    library = factories["federation.Library"](federation_enabled=True)
+    tfs = factories["music.TrackFile"].create_batch(size=5)
     page_conf = {
-        'actor': library.actor,
-        'id': library.url,
-        'page': Paginator(tfs, 5).page(1),
-        'item_serializer': serializers.AudioSerializer,
+        "actor": library.actor,
+        "id": library.url,
+        "page": Paginator(tfs, 5).page(1),
+        "item_serializer": serializers.AudioSerializer,
     }
     page = serializers.CollectionPageSerializer(page_conf)
-    r_mock.get(page.data['id'], json=page.data)
+    r_mock.get(page.data["id"], json=page.data)
 
-    tasks.scan_library_page(library_id=library.pk, page_url=page.data['id'])
+    tasks.scan_library_page(library_id=library.pk, page_url=page.data["id"])
 
-    lts = list(library.tracks.all().order_by('-published_date'))
+    lts = list(library.tracks.all().order_by("-published_date"))
     assert len(lts) == 5
 
 
-def test_scan_page_trigger_next_page_scan_skip_if_same(
-        mocker, factories, r_mock):
+def test_scan_page_trigger_next_page_scan_skip_if_same(mocker, factories, r_mock):
     patched_scan = mocker.patch(
-        'funkwhale_api.federation.tasks.scan_library_page.delay'
+        "funkwhale_api.federation.tasks.scan_library_page.delay"
     )
-    library = factories['federation.Library'](federation_enabled=True)
-    tfs = factories['music.TrackFile'].create_batch(size=1)
+    library = factories["federation.Library"](federation_enabled=True)
+    tfs = factories["music.TrackFile"].create_batch(size=1)
     page_conf = {
-        'actor': library.actor,
-        'id': library.url,
-        'page': Paginator(tfs, 3).page(1),
-        'item_serializer': serializers.AudioSerializer,
+        "actor": library.actor,
+        "id": library.url,
+        "page": Paginator(tfs, 3).page(1),
+        "item_serializer": serializers.AudioSerializer,
     }
     page = serializers.CollectionPageSerializer(page_conf)
     data = page.data
-    data['next'] = data['id']
-    r_mock.get(page.data['id'], json=data)
+    data["next"] = data["id"]
+    r_mock.get(page.data["id"], json=data)
 
-    tasks.scan_library_page(library_id=library.pk, page_url=data['id'])
+    tasks.scan_library_page(library_id=library.pk, page_url=data["id"])
     patched_scan.assert_not_called()
 
 
-def test_scan_page_stops_once_until_is_reached(
-        mocker, factories, r_mock):
-    library = factories['federation.Library'](federation_enabled=True)
-    tfs = list(reversed(factories['music.TrackFile'].create_batch(size=5)))
+def test_scan_page_stops_once_until_is_reached(mocker, factories, r_mock):
+    library = factories["federation.Library"](federation_enabled=True)
+    tfs = list(reversed(factories["music.TrackFile"].create_batch(size=5)))
     page_conf = {
-        'actor': library.actor,
-        'id': library.url,
-        'page': Paginator(tfs, 3).page(1),
-        'item_serializer': serializers.AudioSerializer,
+        "actor": library.actor,
+        "id": library.url,
+        "page": Paginator(tfs, 3).page(1),
+        "item_serializer": serializers.AudioSerializer,
     }
     page = serializers.CollectionPageSerializer(page_conf)
-    r_mock.get(page.data['id'], json=page.data)
+    r_mock.get(page.data["id"], json=page.data)
 
     tasks.scan_library_page(
-        library_id=library.pk,
-        page_url=page.data['id'],
-        until=tfs[1].creation_date)
+        library_id=library.pk, page_url=page.data["id"], until=tfs[1].creation_date
+    )
 
-    lts = list(library.tracks.all().order_by('-published_date'))
+    lts = list(library.tracks.all().order_by("-published_date"))
     assert len(lts) == 2
     for i, tf in enumerate(tfs[:1]):
         assert tf.creation_date == lts[i].published_date
 
 
 def test_clean_federation_music_cache_if_no_listen(preferences, factories):
-    preferences['federation__music_cache_duration'] = 60
-    lt1 = factories['federation.LibraryTrack'](with_audio_file=True)
-    lt2 = factories['federation.LibraryTrack'](with_audio_file=True)
-    lt3 = factories['federation.LibraryTrack'](with_audio_file=True)
-    tf1 = factories['music.TrackFile'](
-        accessed_date=timezone.now(), library_track=lt1)
-    tf2 = factories['music.TrackFile'](
-        accessed_date=timezone.now()-datetime.timedelta(minutes=61),
-        library_track=lt2)
-    tf3 = factories['music.TrackFile'](
-        accessed_date=None, library_track=lt3)
+    preferences["federation__music_cache_duration"] = 60
+    lt1 = factories["federation.LibraryTrack"](with_audio_file=True)
+    lt2 = factories["federation.LibraryTrack"](with_audio_file=True)
+    lt3 = factories["federation.LibraryTrack"](with_audio_file=True)
+    tf1 = factories["music.TrackFile"](accessed_date=timezone.now(), library_track=lt1)
+    tf2 = factories["music.TrackFile"](
+        accessed_date=timezone.now() - datetime.timedelta(minutes=61), library_track=lt2
+    )
+    tf3 = factories["music.TrackFile"](accessed_date=None, library_track=lt3)
     path1 = lt1.audio_file.path
     path2 = lt2.audio_file.path
     path3 = lt3.audio_file.path
@@ -145,22 +134,19 @@ def test_clean_federation_music_cache_if_no_listen(preferences, factories):
     assert os.path.exists(path3) is False
 
 
-def test_clean_federation_music_cache_orphaned(
-        settings, preferences, factories):
-    preferences['federation__music_cache_duration'] = 60
-    path = os.path.join(settings.MEDIA_ROOT, 'federation_cache')
-    keep_path = os.path.join(os.path.join(path, '1a', 'b2'), 'keep.ogg')
-    remove_path = os.path.join(os.path.join(path, 'c3', 'd4'), 'remove.ogg')
+def test_clean_federation_music_cache_orphaned(settings, preferences, factories):
+    preferences["federation__music_cache_duration"] = 60
+    path = os.path.join(settings.MEDIA_ROOT, "federation_cache")
+    keep_path = os.path.join(os.path.join(path, "1a", "b2"), "keep.ogg")
+    remove_path = os.path.join(os.path.join(path, "c3", "d4"), "remove.ogg")
     os.makedirs(os.path.dirname(keep_path), exist_ok=True)
     os.makedirs(os.path.dirname(remove_path), exist_ok=True)
     pathlib.Path(keep_path).touch()
     pathlib.Path(remove_path).touch()
-    lt = factories['federation.LibraryTrack'](
-        with_audio_file=True,
-        audio_file__path=keep_path)
-    tf = factories['music.TrackFile'](
-        library_track=lt,
-        accessed_date=timezone.now())
+    lt = factories["federation.LibraryTrack"](
+        with_audio_file=True, audio_file__path=keep_path
+    )
+    tf = factories["music.TrackFile"](library_track=lt, accessed_date=timezone.now())
 
     tasks.clean_music_cache()
 
diff --git a/api/tests/federation/test_utils.py b/api/tests/federation/test_utils.py
index dc371ad9ed..dbebe0fdc2 100644
--- a/api/tests/federation/test_utils.py
+++ b/api/tests/federation/test_utils.py
@@ -3,12 +3,15 @@ import pytest
 from funkwhale_api.federation import utils
 
 
-@pytest.mark.parametrize('url,path,expected', [
-    ('http://test.com', '/hello', 'http://test.com/hello'),
-    ('http://test.com/', 'hello', 'http://test.com/hello'),
-    ('http://test.com/', '/hello', 'http://test.com/hello'),
-    ('http://test.com', 'hello', 'http://test.com/hello'),
-])
+@pytest.mark.parametrize(
+    "url,path,expected",
+    [
+        ("http://test.com", "/hello", "http://test.com/hello"),
+        ("http://test.com/", "hello", "http://test.com/hello"),
+        ("http://test.com/", "/hello", "http://test.com/hello"),
+        ("http://test.com", "hello", "http://test.com/hello"),
+    ],
+)
 def test_full_url(settings, url, path, expected):
     settings.FUNKWHALE_URL = url
     assert utils.full_url(path) == expected
@@ -16,33 +19,34 @@ def test_full_url(settings, url, path, expected):
 
 def test_extract_headers_from_meta():
     wsgi_headers = {
-        'HTTP_HOST': 'nginx',
-        'HTTP_X_REAL_IP': '172.20.0.4',
-        'HTTP_X_FORWARDED_FOR': '188.165.228.227, 172.20.0.4',
-        'HTTP_X_FORWARDED_PROTO': 'http',
-        'HTTP_X_FORWARDED_HOST': 'localhost:80',
-        'HTTP_X_FORWARDED_PORT': '80',
-        'HTTP_CONNECTION': 'close',
-        'CONTENT_LENGTH': '1155',
-        'CONTENT_TYPE': 'txt/application',
-        'HTTP_SIGNATURE': 'Hello',
-        'HTTP_DATE': 'Sat, 31 Mar 2018 13:53:55 GMT',
-        'HTTP_USER_AGENT': 'http.rb/3.0.0 (Mastodon/2.2.0; +https://mastodon.eliotberriot.com/)'}
+        "HTTP_HOST": "nginx",
+        "HTTP_X_REAL_IP": "172.20.0.4",
+        "HTTP_X_FORWARDED_FOR": "188.165.228.227, 172.20.0.4",
+        "HTTP_X_FORWARDED_PROTO": "http",
+        "HTTP_X_FORWARDED_HOST": "localhost:80",
+        "HTTP_X_FORWARDED_PORT": "80",
+        "HTTP_CONNECTION": "close",
+        "CONTENT_LENGTH": "1155",
+        "CONTENT_TYPE": "txt/application",
+        "HTTP_SIGNATURE": "Hello",
+        "HTTP_DATE": "Sat, 31 Mar 2018 13:53:55 GMT",
+        "HTTP_USER_AGENT": "http.rb/3.0.0 (Mastodon/2.2.0; +https://mastodon.eliotberriot.com/)",
+    }
 
     cleaned_headers = utils.clean_wsgi_headers(wsgi_headers)
 
     expected = {
-        'Host': 'nginx',
-        'X-Real-Ip': '172.20.0.4',
-        'X-Forwarded-For': '188.165.228.227, 172.20.0.4',
-        'X-Forwarded-Proto': 'http',
-        'X-Forwarded-Host': 'localhost:80',
-        'X-Forwarded-Port': '80',
-        'Connection': 'close',
-        'Content-Length': '1155',
-        'Content-Type': 'txt/application',
-        'Signature': 'Hello',
-        'Date': 'Sat, 31 Mar 2018 13:53:55 GMT',
-        'User-Agent': 'http.rb/3.0.0 (Mastodon/2.2.0; +https://mastodon.eliotberriot.com/)'
+        "Host": "nginx",
+        "X-Real-Ip": "172.20.0.4",
+        "X-Forwarded-For": "188.165.228.227, 172.20.0.4",
+        "X-Forwarded-Proto": "http",
+        "X-Forwarded-Host": "localhost:80",
+        "X-Forwarded-Port": "80",
+        "Connection": "close",
+        "Content-Length": "1155",
+        "Content-Type": "txt/application",
+        "Signature": "Hello",
+        "Date": "Sat, 31 Mar 2018 13:53:55 GMT",
+        "User-Agent": "http.rb/3.0.0 (Mastodon/2.2.0; +https://mastodon.eliotberriot.com/)",
     }
     assert cleaned_headers == expected
diff --git a/api/tests/federation/test_views.py b/api/tests/federation/test_views.py
index 04a419aed6..2b0e47cdb1 100644
--- a/api/tests/federation/test_views.py
+++ b/api/tests/federation/test_views.py
@@ -13,315 +13,297 @@ from funkwhale_api.federation import views
 from funkwhale_api.federation import webfinger
 
 
-@pytest.mark.parametrize('view,permissions', [
-    (views.LibraryViewSet, ['federation']),
-    (views.LibraryTrackViewSet, ['federation']),
-])
+@pytest.mark.parametrize(
+    "view,permissions",
+    [
+        (views.LibraryViewSet, ["federation"]),
+        (views.LibraryTrackViewSet, ["federation"]),
+    ],
+)
 def test_permissions(assert_user_permission, view, permissions):
     assert_user_permission(view, permissions)
 
 
-@pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys())
+@pytest.mark.parametrize("system_actor", actors.SYSTEM_ACTORS.keys())
 def test_instance_actors(system_actor, db, api_client):
     actor = actors.SYSTEM_ACTORS[system_actor].get_actor_instance()
-    url = reverse(
-        'federation:instance-actors-detail',
-        kwargs={'actor': system_actor})
+    url = reverse("federation:instance-actors-detail", kwargs={"actor": system_actor})
     response = api_client.get(url)
     serializer = serializers.ActorSerializer(actor)
 
-    if system_actor == 'library':
-        response.data.pop('url')
+    if system_actor == "library":
+        response.data.pop("url")
     assert response.status_code == 200
     assert response.data == serializer.data
 
 
-@pytest.mark.parametrize('route,kwargs', [
-    ('instance-actors-outbox', {'actor': 'library'}),
-    ('instance-actors-inbox', {'actor': 'library'}),
-    ('instance-actors-detail', {'actor': 'library'}),
-    ('well-known-webfinger', {}),
-])
+@pytest.mark.parametrize(
+    "route,kwargs",
+    [
+        ("instance-actors-outbox", {"actor": "library"}),
+        ("instance-actors-inbox", {"actor": "library"}),
+        ("instance-actors-detail", {"actor": "library"}),
+        ("well-known-webfinger", {}),
+    ],
+)
 def test_instance_endpoints_405_if_federation_disabled(
-        authenticated_actor, db, preferences, api_client, route, kwargs):
-    preferences['federation__enabled'] = False
-    url = reverse('federation:{}'.format(route), kwargs=kwargs)
+    authenticated_actor, db, preferences, api_client, route, kwargs
+):
+    preferences["federation__enabled"] = False
+    url = reverse("federation:{}".format(route), kwargs=kwargs)
     response = api_client.get(url)
 
     assert response.status_code == 405
 
 
-def test_wellknown_webfinger_validates_resource(
-        db, api_client, settings, mocker):
-    clean = mocker.spy(webfinger, 'clean_resource')
-    url = reverse('federation:well-known-webfinger')
-    response = api_client.get(url, data={'resource': 'something'})
+def test_wellknown_webfinger_validates_resource(db, api_client, settings, mocker):
+    clean = mocker.spy(webfinger, "clean_resource")
+    url = reverse("federation:well-known-webfinger")
+    response = api_client.get(url, data={"resource": "something"})
 
-    clean.assert_called_once_with('something')
-    assert url == '/.well-known/webfinger'
+    clean.assert_called_once_with("something")
+    assert url == "/.well-known/webfinger"
     assert response.status_code == 400
-    assert response.data['errors']['resource'] == (
-        'Missing webfinger resource type'
-    )
+    assert response.data["errors"]["resource"] == ("Missing webfinger resource type")
 
 
-@pytest.mark.parametrize('system_actor', actors.SYSTEM_ACTORS.keys())
-def test_wellknown_webfinger_system(
-        system_actor, db, api_client, settings, mocker):
+@pytest.mark.parametrize("system_actor", actors.SYSTEM_ACTORS.keys())
+def test_wellknown_webfinger_system(system_actor, db, api_client, settings, mocker):
     actor = actors.SYSTEM_ACTORS[system_actor].get_actor_instance()
-    url = reverse('federation:well-known-webfinger')
+    url = reverse("federation:well-known-webfinger")
     response = api_client.get(
         url,
-        data={'resource': 'acct:{}'.format(actor.webfinger_subject)},
-        HTTP_ACCEPT='application/jrd+json',
+        data={"resource": "acct:{}".format(actor.webfinger_subject)},
+        HTTP_ACCEPT="application/jrd+json",
     )
     serializer = serializers.ActorWebfingerSerializer(actor)
 
     assert response.status_code == 200
-    assert response['Content-Type'] == 'application/jrd+json'
+    assert response["Content-Type"] == "application/jrd+json"
     assert response.data == serializer.data
 
 
 def test_wellknown_nodeinfo(db, preferences, api_client, settings):
     expected = {
-        'links': [
+        "links": [
             {
-                'rel': 'http://nodeinfo.diaspora.software/ns/schema/2.0',
-                'href': '{}{}'.format(
-                    settings.FUNKWHALE_URL,
-                    reverse('api:v1:instance:nodeinfo-2.0')
-                )
+                "rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
+                "href": "{}{}".format(
+                    settings.FUNKWHALE_URL, reverse("api:v1:instance:nodeinfo-2.0")
+                ),
             }
         ]
     }
-    url = reverse('federation:well-known-nodeinfo')
-    response = api_client.get(url, HTTP_ACCEPT='application/jrd+json')
+    url = reverse("federation:well-known-nodeinfo")
+    response = api_client.get(url, HTTP_ACCEPT="application/jrd+json")
     assert response.status_code == 200
-    assert response['Content-Type'] == 'application/jrd+json'
+    assert response["Content-Type"] == "application/jrd+json"
     assert response.data == expected
 
 
 def test_wellknown_nodeinfo_disabled(db, preferences, api_client):
-    preferences['instance__nodeinfo_enabled'] = False
-    url = reverse('federation:well-known-nodeinfo')
+    preferences["instance__nodeinfo_enabled"] = False
+    url = reverse("federation:well-known-nodeinfo")
     response = api_client.get(url)
     assert response.status_code == 404
 
 
-def test_audio_file_list_requires_authenticated_actor(
-        db, preferences, api_client):
-    preferences['federation__music_needs_approval'] = True
-    url = reverse('federation:music:files-list')
+def test_audio_file_list_requires_authenticated_actor(db, preferences, api_client):
+    preferences["federation__music_needs_approval"] = True
+    url = reverse("federation:music:files-list")
     response = api_client.get(url)
 
     assert response.status_code == 403
 
 
-def test_audio_file_list_actor_no_page(
-        db, preferences, api_client, factories):
-    preferences['federation__music_needs_approval'] = False
-    preferences['federation__collection_page_size'] = 2
-    library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    tfs = factories['music.TrackFile'].create_batch(size=5)
+def test_audio_file_list_actor_no_page(db, preferences, api_client, factories):
+    preferences["federation__music_needs_approval"] = False
+    preferences["federation__collection_page_size"] = 2
+    library = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    tfs = factories["music.TrackFile"].create_batch(size=5)
     conf = {
-        'id': utils.full_url(reverse('federation:music:files-list')),
-        'page_size': 2,
-        'items': list(reversed(tfs)),  # we order by -creation_date
-        'item_serializer': serializers.AudioSerializer,
-        'actor': library
+        "id": utils.full_url(reverse("federation:music:files-list")),
+        "page_size": 2,
+        "items": list(reversed(tfs)),  # we order by -creation_date
+        "item_serializer": serializers.AudioSerializer,
+        "actor": library,
     }
     expected = serializers.PaginatedCollectionSerializer(conf).data
-    url = reverse('federation:music:files-list')
+    url = reverse("federation:music:files-list")
     response = api_client.get(url)
 
     assert response.status_code == 200
     assert response.data == expected
 
 
-def test_audio_file_list_actor_page(
-        db, preferences, api_client, factories):
-    preferences['federation__music_needs_approval'] = False
-    preferences['federation__collection_page_size'] = 2
-    library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    tfs = factories['music.TrackFile'].create_batch(size=5)
+def test_audio_file_list_actor_page(db, preferences, api_client, factories):
+    preferences["federation__music_needs_approval"] = False
+    preferences["federation__collection_page_size"] = 2
+    library = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    tfs = factories["music.TrackFile"].create_batch(size=5)
     conf = {
-        'id': utils.full_url(reverse('federation:music:files-list')),
-        'page': Paginator(list(reversed(tfs)), 2).page(2),
-        'item_serializer': serializers.AudioSerializer,
-        'actor': library
+        "id": utils.full_url(reverse("federation:music:files-list")),
+        "page": Paginator(list(reversed(tfs)), 2).page(2),
+        "item_serializer": serializers.AudioSerializer,
+        "actor": library,
     }
     expected = serializers.CollectionPageSerializer(conf).data
-    url = reverse('federation:music:files-list')
-    response = api_client.get(url, data={'page': 2})
+    url = reverse("federation:music:files-list")
+    response = api_client.get(url, data={"page": 2})
 
     assert response.status_code == 200
     assert response.data == expected
 
 
 def test_audio_file_list_actor_page_exclude_federated_files(
-        db, preferences, api_client, factories):
-    preferences['federation__music_needs_approval'] = False
-    library = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    tfs = factories['music.TrackFile'].create_batch(size=5, federation=True)
+    db, preferences, api_client, factories
+):
+    preferences["federation__music_needs_approval"] = False
+    library = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    tfs = factories["music.TrackFile"].create_batch(size=5, federation=True)
 
-    url = reverse('federation:music:files-list')
+    url = reverse("federation:music:files-list")
     response = api_client.get(url)
 
     assert response.status_code == 200
-    assert response.data['totalItems'] == 0
+    assert response.data["totalItems"] == 0
 
 
-def test_audio_file_list_actor_page_error(
-        db, preferences, api_client, factories):
-    preferences['federation__music_needs_approval'] = False
-    url = reverse('federation:music:files-list')
-    response = api_client.get(url, data={'page': 'nope'})
+def test_audio_file_list_actor_page_error(db, preferences, api_client, factories):
+    preferences["federation__music_needs_approval"] = False
+    url = reverse("federation:music:files-list")
+    response = api_client.get(url, data={"page": "nope"})
 
     assert response.status_code == 400
 
 
 def test_audio_file_list_actor_page_error_too_far(
-        db, preferences, api_client, factories):
-    preferences['federation__music_needs_approval'] = False
-    url = reverse('federation:music:files-list')
-    response = api_client.get(url, data={'page': 5000})
+    db, preferences, api_client, factories
+):
+    preferences["federation__music_needs_approval"] = False
+    url = reverse("federation:music:files-list")
+    response = api_client.get(url, data={"page": 5000})
 
     assert response.status_code == 404
 
 
 def test_library_actor_includes_library_link(db, preferences, api_client):
-    actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    url = reverse(
-        'federation:instance-actors-detail',
-        kwargs={'actor': 'library'})
+    actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    url = reverse("federation:instance-actors-detail", kwargs={"actor": "library"})
     response = api_client.get(url)
     expected_links = [
         {
-            'type': 'Link',
-            'name': 'library',
-            'mediaType': 'application/activity+json',
-            'href': utils.full_url(reverse('federation:music:files-list'))
+            "type": "Link",
+            "name": "library",
+            "mediaType": "application/activity+json",
+            "href": utils.full_url(reverse("federation:music:files-list")),
         }
     ]
     assert response.status_code == 200
-    assert response.data['url'] == expected_links
+    assert response.data["url"] == expected_links
 
 
 def test_can_fetch_library(superuser_api_client, mocker):
-    result = {'test': 'test'}
+    result = {"test": "test"}
     scan = mocker.patch(
-        'funkwhale_api.federation.library.scan_from_account_name',
-        return_value=result)
+        "funkwhale_api.federation.library.scan_from_account_name", return_value=result
+    )
 
-    url = reverse('api:v1:federation:libraries-fetch')
-    response = superuser_api_client.get(
-        url, data={'account': 'test@test.library'})
+    url = reverse("api:v1:federation:libraries-fetch")
+    response = superuser_api_client.get(url, data={"account": "test@test.library"})
 
     assert response.status_code == 200
     assert response.data == result
-    scan.assert_called_once_with('test@test.library')
+    scan.assert_called_once_with("test@test.library")
 
 
 def test_follow_library(superuser_api_client, mocker, factories, r_mock):
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    actor = factories['federation.Actor']()
-    follow = {'test': 'follow'}
-    on_commit = mocker.patch(
-        'funkwhale_api.common.utils.on_commit')
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    actor = factories["federation.Actor"]()
+    follow = {"test": "follow"}
+    on_commit = mocker.patch("funkwhale_api.common.utils.on_commit")
     actor_data = serializers.ActorSerializer(actor).data
-    actor_data['url'] = [{
-        'href': 'https://test.library',
-        'name': 'library',
-        'type': 'Link',
-    }]
+    actor_data["url"] = [
+        {"href": "https://test.library", "name": "library", "type": "Link"}
+    ]
     library_conf = {
-        'id': 'https://test.library',
-        'items': range(10),
-        'actor': actor,
-        'page_size': 5,
+        "id": "https://test.library",
+        "items": range(10),
+        "actor": actor,
+        "page_size": 5,
     }
     library_data = serializers.PaginatedCollectionSerializer(library_conf).data
     r_mock.get(actor.url, json=actor_data)
-    r_mock.get('https://test.library', json=library_data)
+    r_mock.get("https://test.library", json=library_data)
     data = {
-        'actor': actor.url,
-        'autoimport': False,
-        'federation_enabled': True,
-        'download_files': False,
+        "actor": actor.url,
+        "autoimport": False,
+        "federation_enabled": True,
+        "download_files": False,
     }
 
-    url = reverse('api:v1:federation:libraries-list')
-    response = superuser_api_client.post(
-        url, data)
+    url = reverse("api:v1:federation:libraries-list")
+    response = superuser_api_client.post(url, data)
 
     assert response.status_code == 201
 
-    follow = models.Follow.objects.get(
-        actor=library_actor,
-        target=actor,
-        approved=None,
-    )
+    follow = models.Follow.objects.get(actor=library_actor, target=actor, approved=None)
     library = follow.library
 
-    assert response.data == serializers.APILibraryCreateSerializer(
-        library).data
+    assert response.data == serializers.APILibraryCreateSerializer(library).data
 
     on_commit.assert_called_once_with(
         activity.deliver,
         serializers.FollowSerializer(follow).data,
         on_behalf_of=library_actor,
-        to=[actor.url]
+        to=[actor.url],
     )
 
 
 def test_can_list_system_actor_following(factories, superuser_api_client):
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    follow1 = factories['federation.Follow'](actor=library_actor)
-    follow2 = factories['federation.Follow']()
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    follow1 = factories["federation.Follow"](actor=library_actor)
+    follow2 = factories["federation.Follow"]()
 
-    url = reverse('api:v1:federation:libraries-following')
+    url = reverse("api:v1:federation:libraries-following")
     response = superuser_api_client.get(url)
 
     assert response.status_code == 200
-    assert response.data['results'] == [
-        serializers.APIFollowSerializer(follow1).data
-    ]
+    assert response.data["results"] == [serializers.APIFollowSerializer(follow1).data]
 
 
 def test_can_list_system_actor_followers(factories, superuser_api_client):
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    follow1 = factories['federation.Follow'](actor=library_actor)
-    follow2 = factories['federation.Follow'](target=library_actor)
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    follow1 = factories["federation.Follow"](actor=library_actor)
+    follow2 = factories["federation.Follow"](target=library_actor)
 
-    url = reverse('api:v1:federation:libraries-followers')
+    url = reverse("api:v1:federation:libraries-followers")
     response = superuser_api_client.get(url)
 
     assert response.status_code == 200
-    assert response.data['results'] == [
-        serializers.APIFollowSerializer(follow2).data
-    ]
+    assert response.data["results"] == [serializers.APIFollowSerializer(follow2).data]
 
 
 def test_can_list_libraries(factories, superuser_api_client):
-    library1 = factories['federation.Library']()
-    library2 = factories['federation.Library']()
+    library1 = factories["federation.Library"]()
+    library2 = factories["federation.Library"]()
 
-    url = reverse('api:v1:federation:libraries-list')
+    url = reverse("api:v1:federation:libraries-list")
     response = superuser_api_client.get(url)
 
     assert response.status_code == 200
-    assert response.data['results'] == [
+    assert response.data["results"] == [
         serializers.APILibrarySerializer(library1).data,
         serializers.APILibrarySerializer(library2).data,
     ]
 
 
 def test_can_detail_library(factories, superuser_api_client):
-    library = factories['federation.Library']()
+    library = factories["federation.Library"]()
 
     url = reverse(
-        'api:v1:federation:libraries-detail',
-        kwargs={'uuid': str(library.uuid)})
+        "api:v1:federation:libraries-detail", kwargs={"uuid": str(library.uuid)}
+    )
     response = superuser_api_client.get(url)
 
     assert response.status_code == 200
@@ -329,15 +311,15 @@ def test_can_detail_library(factories, superuser_api_client):
 
 
 def test_can_patch_library(factories, superuser_api_client):
-    library = factories['federation.Library']()
+    library = factories["federation.Library"]()
     data = {
-        'federation_enabled': not library.federation_enabled,
-        'download_files': not library.download_files,
-        'autoimport': not library.autoimport,
+        "federation_enabled": not library.federation_enabled,
+        "download_files": not library.download_files,
+        "autoimport": not library.autoimport,
     }
     url = reverse(
-        'api:v1:federation:libraries-detail',
-        kwargs={'uuid': str(library.uuid)})
+        "api:v1:federation:libraries-detail", kwargs={"uuid": str(library.uuid)}
+    )
     response = superuser_api_client.patch(url, data)
 
     assert response.status_code == 200
@@ -349,55 +331,49 @@ def test_can_patch_library(factories, superuser_api_client):
 
 def test_scan_library(factories, mocker, superuser_api_client):
     scan = mocker.patch(
-        'funkwhale_api.federation.tasks.scan_library.delay',
-        return_value=mocker.Mock(id='id'))
-    library = factories['federation.Library']()
+        "funkwhale_api.federation.tasks.scan_library.delay",
+        return_value=mocker.Mock(id="id"),
+    )
+    library = factories["federation.Library"]()
     now = timezone.now()
-    data = {
-        'until': now,
-    }
+    data = {"until": now}
     url = reverse(
-        'api:v1:federation:libraries-scan',
-        kwargs={'uuid': str(library.uuid)})
+        "api:v1:federation:libraries-scan", kwargs={"uuid": str(library.uuid)}
+    )
     response = superuser_api_client.post(url, data)
 
     assert response.status_code == 200
-    assert response.data == {'task': 'id'}
-    scan.assert_called_once_with(
-        library_id=library.pk,
-        until=now
-    )
+    assert response.data == {"task": "id"}
+    scan.assert_called_once_with(library_id=library.pk, until=now)
 
 
 def test_list_library_tracks(factories, superuser_api_client):
-    library = factories['federation.Library']()
-    lts = list(reversed(factories['federation.LibraryTrack'].create_batch(
-        size=5, library=library)))
-    factories['federation.LibraryTrack'].create_batch(size=5)
-    url = reverse('api:v1:federation:library-tracks-list')
-    response = superuser_api_client.get(url, {'library': library.uuid})
+    library = factories["federation.Library"]()
+    lts = list(
+        reversed(
+            factories["federation.LibraryTrack"].create_batch(size=5, library=library)
+        )
+    )
+    factories["federation.LibraryTrack"].create_batch(size=5)
+    url = reverse("api:v1:federation:library-tracks-list")
+    response = superuser_api_client.get(url, {"library": library.uuid})
 
     assert response.status_code == 200
     assert response.data == {
-        'results': serializers.APILibraryTrackSerializer(lts, many=True).data,
-        'count': 5,
-        'previous': None,
-        'next': None,
+        "results": serializers.APILibraryTrackSerializer(lts, many=True).data,
+        "count": 5,
+        "previous": None,
+        "next": None,
     }
 
 
 def test_can_update_follow_status(factories, superuser_api_client, mocker):
-    patched_accept = mocker.patch(
-        'funkwhale_api.federation.activity.accept_follow'
-    )
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    follow = factories['federation.Follow'](target=library_actor)
+    patched_accept = mocker.patch("funkwhale_api.federation.activity.accept_follow")
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    follow = factories["federation.Follow"](target=library_actor)
 
-    payload = {
-        'follow': follow.pk,
-        'approved': True
-    }
-    url = reverse('api:v1:federation:libraries-followers')
+    payload = {"follow": follow.pk, "approved": True}
+    url = reverse("api:v1:federation:libraries-followers")
     response = superuser_api_client.patch(url, payload)
     follow.refresh_from_db()
 
@@ -407,45 +383,33 @@ def test_can_update_follow_status(factories, superuser_api_client, mocker):
 
 
 def test_can_filter_pending_follows(factories, superuser_api_client):
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    follow = factories['federation.Follow'](
-        target=library_actor,
-        approved=True)
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    follow = factories["federation.Follow"](target=library_actor, approved=True)
 
-    params = {'pending': True}
-    url = reverse('api:v1:federation:libraries-followers')
+    params = {"pending": True}
+    url = reverse("api:v1:federation:libraries-followers")
     response = superuser_api_client.get(url, params)
 
     assert response.status_code == 200
-    assert len(response.data['results']) == 0
+    assert len(response.data["results"]) == 0
 
 
-def test_library_track_action_import(
-        factories, superuser_api_client, mocker):
-    lt1 = factories['federation.LibraryTrack']()
-    lt2 = factories['federation.LibraryTrack'](library=lt1.library)
-    lt3 = factories['federation.LibraryTrack']()
-    lt4 = factories['federation.LibraryTrack'](library=lt3.library)
-    mocked_run = mocker.patch(
-        'funkwhale_api.music.tasks.import_batch_run.delay')
+def test_library_track_action_import(factories, superuser_api_client, mocker):
+    lt1 = factories["federation.LibraryTrack"]()
+    lt2 = factories["federation.LibraryTrack"](library=lt1.library)
+    lt3 = factories["federation.LibraryTrack"]()
+    lt4 = factories["federation.LibraryTrack"](library=lt3.library)
+    mocked_run = mocker.patch("funkwhale_api.music.tasks.import_batch_run.delay")
 
     payload = {
-        'objects': 'all',
-        'action': 'import',
-        'filters': {
-            'library': lt1.library.uuid
-        }
-    }
-    url = reverse('api:v1:federation:library-tracks-action')
-    response = superuser_api_client.post(url, payload, format='json')
-    batch = superuser_api_client.user.imports.latest('id')
-    expected = {
-        'updated': 2,
-        'action': 'import',
-        'result': {
-            'batch': {'id': batch.pk}
-        }
+        "objects": "all",
+        "action": "import",
+        "filters": {"library": lt1.library.uuid},
     }
+    url = reverse("api:v1:federation:library-tracks-action")
+    response = superuser_api_client.post(url, payload, format="json")
+    batch = superuser_api_client.user.imports.latest("id")
+    expected = {"updated": 2, "action": "import", "result": {"batch": {"id": batch.pk}}}
 
     imported_lts = [lt1, lt2]
     assert response.status_code == 200
diff --git a/api/tests/federation/test_webfinger.py b/api/tests/federation/test_webfinger.py
index 4b8dca207a..1b7db92789 100644
--- a/api/tests/federation/test_webfinger.py
+++ b/api/tests/federation/test_webfinger.py
@@ -7,16 +7,19 @@ from funkwhale_api.federation import webfinger
 
 
 def test_webfinger_clean_resource():
-    t, r = webfinger.clean_resource('acct:service@test.federation')
-    assert t == 'acct'
-    assert r == 'service@test.federation'
-
-
-@pytest.mark.parametrize('resource,message', [
-    ('', 'Invalid resource string'),
-    ('service@test.com', 'Missing webfinger resource type'),
-    ('noop:service@test.com', 'Invalid webfinger resource type'),
-])
+    t, r = webfinger.clean_resource("acct:service@test.federation")
+    assert t == "acct"
+    assert r == "service@test.federation"
+
+
+@pytest.mark.parametrize(
+    "resource,message",
+    [
+        ("", "Invalid resource string"),
+        ("service@test.com", "Missing webfinger resource type"),
+        ("noop:service@test.com", "Invalid webfinger resource type"),
+    ],
+)
 def test_webfinger_clean_resource_errors(resource, message):
     with pytest.raises(forms.ValidationError) as excinfo:
         webfinger.clean_resource(resource)
@@ -25,16 +28,19 @@ def test_webfinger_clean_resource_errors(resource, message):
 
 
 def test_webfinger_clean_acct(settings):
-    username, hostname = webfinger.clean_acct('library@test.federation')
-    assert username == 'library'
-    assert hostname == 'test.federation'
-
-
-@pytest.mark.parametrize('resource,message', [
-    ('service', 'Invalid format'),
-    ('service@test.com', 'Invalid hostname test.com'),
-    ('noop@test.federation', 'Invalid account'),
-])
+    username, hostname = webfinger.clean_acct("library@test.federation")
+    assert username == "library"
+    assert hostname == "test.federation"
+
+
+@pytest.mark.parametrize(
+    "resource,message",
+    [
+        ("service", "Invalid format"),
+        ("service@test.com", "Invalid hostname test.com"),
+        ("noop@test.federation", "Invalid account"),
+    ],
+)
 def test_webfinger_clean_acct_errors(resource, message, settings):
     with pytest.raises(forms.ValidationError) as excinfo:
         webfinger.clean_resource(resource)
@@ -43,26 +49,24 @@ def test_webfinger_clean_acct_errors(resource, message, settings):
 
 
 def test_webfinger_get_resource(r_mock):
-    resource = 'acct:test@test.webfinger'
+    resource = "acct:test@test.webfinger"
     payload = {
-        'subject': resource,
-        'aliases': ['https://test.webfinger'],
-        'links': [
+        "subject": resource,
+        "aliases": ["https://test.webfinger"],
+        "links": [
             {
-                'rel': 'self',
-                'type': 'application/activity+json',
-                'href': 'https://test.webfinger/user/test'
+                "rel": "self",
+                "type": "application/activity+json",
+                "href": "https://test.webfinger/user/test",
             }
-        ]
+        ],
     }
     r_mock.get(
-        'https://test.webfinger/.well-known/webfinger?resource={}'.format(
-            resource
-        ),
-        json=payload
+        "https://test.webfinger/.well-known/webfinger?resource={}".format(resource),
+        json=payload,
     )
 
-    data = webfinger.get_resource('acct:test@test.webfinger')
+    data = webfinger.get_resource("acct:test@test.webfinger")
 
-    assert data['actor_url'] == 'https://test.webfinger/user/test'
-    assert data['subject'] == resource
+    assert data["actor_url"] == "https://test.webfinger/user/test"
+    assert data["subject"] == resource
diff --git a/api/tests/history/test_activity.py b/api/tests/history/test_activity.py
index 04000604b2..c3cca5925e 100644
--- a/api/tests/history/test_activity.py
+++ b/api/tests/history/test_activity.py
@@ -5,15 +5,14 @@ from funkwhale_api.history import activities
 
 
 def test_get_listening_activity_url(settings, factories):
-    listening = factories['history.Listening']()
+    listening = factories["history.Listening"]()
     user_url = listening.user.get_activity_url()
-    expected = '{}/listenings/tracks/{}'.format(
-        user_url, listening.pk)
+    expected = "{}/listenings/tracks/{}".format(user_url, listening.pk)
     assert listening.get_activity_url() == expected
 
 
 def test_activity_listening_serializer(factories):
-    listening = factories['history.Listening']()
+    listening = factories["history.Listening"]()
 
     actor = UserActivitySerializer(listening.user).data
     field = serializers.serializers.DateTimeField()
@@ -32,44 +31,31 @@ def test_activity_listening_serializer(factories):
 
 
 def test_track_listening_serializer_is_connected(activity_registry):
-    conf = activity_registry['history.Listening']
-    assert conf['serializer'] == serializers.ListeningActivitySerializer
+    conf = activity_registry["history.Listening"]
+    assert conf["serializer"] == serializers.ListeningActivitySerializer
 
 
-def test_track_listening_serializer_instance_activity_consumer(
-        activity_registry):
-    conf = activity_registry['history.Listening']
+def test_track_listening_serializer_instance_activity_consumer(activity_registry):
+    conf = activity_registry["history.Listening"]
     consumer = activities.broadcast_listening_to_instance_activity
-    assert consumer in conf['consumers']
+    assert consumer in conf["consumers"]
 
 
-def test_broadcast_listening_to_instance_activity(
-        factories, mocker):
-    p = mocker.patch('funkwhale_api.common.channels.group_send')
-    listening = factories['history.Listening']()
+def test_broadcast_listening_to_instance_activity(factories, mocker):
+    p = mocker.patch("funkwhale_api.common.channels.group_send")
+    listening = factories["history.Listening"]()
     data = serializers.ListeningActivitySerializer(listening).data
     consumer = activities.broadcast_listening_to_instance_activity
-    message = {
-        "type": 'event.send',
-        "text": '',
-        "data": data
-    }
+    message = {"type": "event.send", "text": "", "data": data}
     consumer(data=data, obj=listening)
-    p.assert_called_once_with('instance_activity', message)
+    p.assert_called_once_with("instance_activity", message)
 
 
-def test_broadcast_listening_to_instance_activity_private(
-        factories, mocker):
-    p = mocker.patch('funkwhale_api.common.channels.group_send')
-    listening = factories['history.Listening'](
-        user__privacy_level='me'
-    )
+def test_broadcast_listening_to_instance_activity_private(factories, mocker):
+    p = mocker.patch("funkwhale_api.common.channels.group_send")
+    listening = factories["history.Listening"](user__privacy_level="me")
     data = serializers.ListeningActivitySerializer(listening).data
     consumer = activities.broadcast_listening_to_instance_activity
-    message = {
-        "type": 'event.send',
-        "text": '',
-        "data": data
-    }
+    message = {"type": "event.send", "text": "", "data": data}
     consumer(data=data, obj=listening)
     p.assert_not_called()
diff --git a/api/tests/history/test_history.py b/api/tests/history/test_history.py
index 2027255963..016c2a9878 100644
--- a/api/tests/history/test_history.py
+++ b/api/tests/history/test_history.py
@@ -8,36 +8,34 @@ from funkwhale_api.history import models
 
 
 def test_can_create_listening(factories):
-    track = factories['music.Track']()
-    user = factories['users.User']()
+    track = factories["music.Track"]()
+    user = factories["users.User"]()
     now = timezone.now()
     l = models.Listening.objects.create(user=user, track=track)
 
 
 def test_logged_in_user_can_create_listening_via_api(
-        logged_in_client, factories, activity_muted):
-    track = factories['music.Track']()
+    logged_in_client, factories, activity_muted
+):
+    track = factories["music.Track"]()
 
-    url = reverse('api:v1:history:listenings-list')
-    response = logged_in_client.post(url, {
-        'track': track.pk,
-    })
+    url = reverse("api:v1:history:listenings-list")
+    response = logged_in_client.post(url, {"track": track.pk})
 
-    listening = models.Listening.objects.latest('id')
+    listening = models.Listening.objects.latest("id")
 
     assert listening.track == track
     assert listening.user == logged_in_client.user
 
 
 def test_adding_listening_calls_activity_record(
-        factories, logged_in_client, activity_muted):
-    track = factories['music.Track']()
+    factories, logged_in_client, activity_muted
+):
+    track = factories["music.Track"]()
 
-    url = reverse('api:v1:history:listenings-list')
-    response = logged_in_client.post(url, {
-        'track': track.pk,
-    })
+    url = reverse("api:v1:history:listenings-list")
+    response = logged_in_client.post(url, {"track": track.pk})
 
-    listening = models.Listening.objects.latest('id')
+    listening = models.Listening.objects.latest("id")
 
     activity_muted.assert_called_once_with(listening)
diff --git a/api/tests/instance/test_nodeinfo.py b/api/tests/instance/test_nodeinfo.py
index 87b8882880..3c2fa862ee 100644
--- a/api/tests/instance/test_nodeinfo.py
+++ b/api/tests/instance/test_nodeinfo.py
@@ -6,102 +6,76 @@ from funkwhale_api.instance import nodeinfo
 
 
 def test_nodeinfo_dump(preferences, mocker):
-    preferences['instance__nodeinfo_stats_enabled'] = True
+    preferences["instance__nodeinfo_stats_enabled"] = True
     stats = {
-        'users': 1,
-        'tracks': 2,
-        'albums': 3,
-        'artists': 4,
-        'track_favorites': 5,
-        'music_duration': 6,
-        'listenings': 7,
+        "users": 1,
+        "tracks": 2,
+        "albums": 3,
+        "artists": 4,
+        "track_favorites": 5,
+        "music_duration": 6,
+        "listenings": 7,
     }
-    mocker.patch('funkwhale_api.instance.stats.get', return_value=stats)
+    mocker.patch("funkwhale_api.instance.stats.get", return_value=stats)
 
     expected = {
-        'version': '2.0',
-        'software': {
-            'name': 'funkwhale',
-            'version': funkwhale_api.__version__
-        },
-        'protocols': ['activitypub'],
-        'services': {
-            'inbound': [],
-            'outbound': []
-        },
-        'openRegistrations': preferences['users__registration_enabled'],
-        'usage': {
-            'users': {
-                'total': stats['users'],
-            }
-        },
-        'metadata': {
-            'private': preferences['instance__nodeinfo_private'],
-            'shortDescription': preferences['instance__short_description'],
-            'longDescription': preferences['instance__long_description'],
-            'nodeName': preferences['instance__name'],
-            'library': {
-                'federationEnabled': preferences['federation__enabled'],
-                'federationNeedsApproval': preferences['federation__music_needs_approval'],
-                'anonymousCanListen': preferences['common__api_authentication_required'],
-                'tracks': {
-                    'total': stats['tracks'],
-                },
-                'artists': {
-                    'total': stats['artists'],
-                },
-                'albums': {
-                    'total': stats['albums'],
-                },
-                'music': {
-                    'hours': stats['music_duration']
-                },
+        "version": "2.0",
+        "software": {"name": "funkwhale", "version": funkwhale_api.__version__},
+        "protocols": ["activitypub"],
+        "services": {"inbound": [], "outbound": []},
+        "openRegistrations": preferences["users__registration_enabled"],
+        "usage": {"users": {"total": stats["users"]}},
+        "metadata": {
+            "private": preferences["instance__nodeinfo_private"],
+            "shortDescription": preferences["instance__short_description"],
+            "longDescription": preferences["instance__long_description"],
+            "nodeName": preferences["instance__name"],
+            "library": {
+                "federationEnabled": preferences["federation__enabled"],
+                "federationNeedsApproval": preferences[
+                    "federation__music_needs_approval"
+                ],
+                "anonymousCanListen": preferences[
+                    "common__api_authentication_required"
+                ],
+                "tracks": {"total": stats["tracks"]},
+                "artists": {"total": stats["artists"]},
+                "albums": {"total": stats["albums"]},
+                "music": {"hours": stats["music_duration"]},
             },
-            'usage': {
-                'favorites': {
-                    'tracks': {
-                        'total': stats['track_favorites'],
-                    }
-                },
-                'listenings': {
-                    'total': stats['listenings']
-                }
-            }
-        }
+            "usage": {
+                "favorites": {"tracks": {"total": stats["track_favorites"]}},
+                "listenings": {"total": stats["listenings"]},
+            },
+        },
     }
     assert nodeinfo.get() == expected
 
 
 def test_nodeinfo_dump_stats_disabled(preferences, mocker):
-    preferences['instance__nodeinfo_stats_enabled'] = False
+    preferences["instance__nodeinfo_stats_enabled"] = False
 
     expected = {
-        'version': '2.0',
-        'software': {
-            'name': 'funkwhale',
-            'version': funkwhale_api.__version__
-        },
-        'protocols': ['activitypub'],
-        'services': {
-            'inbound': [],
-            'outbound': []
-        },
-        'openRegistrations': preferences['users__registration_enabled'],
-        'usage': {
-            'users': {
-                'total': 0,
-            }
-        },
-        'metadata': {
-            'private': preferences['instance__nodeinfo_private'],
-            'shortDescription': preferences['instance__short_description'],
-            'longDescription': preferences['instance__long_description'],
-            'nodeName': preferences['instance__name'],
-            'library': {
-                'federationEnabled': preferences['federation__enabled'],
-                'federationNeedsApproval': preferences['federation__music_needs_approval'],
-                'anonymousCanListen': preferences['common__api_authentication_required'],
+        "version": "2.0",
+        "software": {"name": "funkwhale", "version": funkwhale_api.__version__},
+        "protocols": ["activitypub"],
+        "services": {"inbound": [], "outbound": []},
+        "openRegistrations": preferences["users__registration_enabled"],
+        "usage": {"users": {"total": 0}},
+        "metadata": {
+            "private": preferences["instance__nodeinfo_private"],
+            "shortDescription": preferences["instance__short_description"],
+            "longDescription": preferences["instance__long_description"],
+            "nodeName": preferences["instance__name"],
+            "library": {
+                "federationEnabled": preferences["federation__enabled"],
+                "federationNeedsApproval": preferences[
+                    "federation__music_needs_approval"
+                ],
+                "anonymousCanListen": preferences[
+                    "common__api_authentication_required"
+                ],
             },
-        }
+        },
     }
     assert nodeinfo.get() == expected
diff --git a/api/tests/instance/test_preferences.py b/api/tests/instance/test_preferences.py
index beb8e6d33f..85932145b5 100644
--- a/api/tests/instance/test_preferences.py
+++ b/api/tests/instance/test_preferences.py
@@ -6,12 +6,13 @@ from dynamic_preferences.api import serializers
 
 
 def test_can_list_settings_via_api(preferences, api_client):
-    url = reverse('api:v1:instance:settings')
+    url = reverse("api:v1:instance:settings")
     all_preferences = preferences.model.objects.all()
     expected_preferences = {
         p.preference.identifier(): p
         for p in all_preferences
-        if getattr(p.preference, 'show_in_api', False)}
+        if getattr(p.preference, "show_in_api", False)
+    }
 
     assert len(expected_preferences) > 0
 
@@ -20,15 +21,18 @@ def test_can_list_settings_via_api(preferences, api_client):
     assert len(response.data) == len(expected_preferences)
 
     for p in response.data:
-        i = '__'.join([p['section'], p['name']])
+        i = "__".join([p["section"], p["name"]])
         assert i in expected_preferences
 
 
-@pytest.mark.parametrize('pref,value', [
-    ('instance__name', 'My instance'),
-    ('instance__short_description', 'For music lovers'),
-    ('instance__long_description', 'For real music lovers'),
-])
+@pytest.mark.parametrize(
+    "pref,value",
+    [
+        ("instance__name", "My instance"),
+        ("instance__short_description", "For music lovers"),
+        ("instance__long_description", "For real music lovers"),
+    ],
+)
 def test_instance_settings(pref, value, preferences):
     preferences[pref] = value
 
diff --git a/api/tests/instance/test_stats.py b/api/tests/instance/test_stats.py
index 6063e93005..9e60d1cdd4 100644
--- a/api/tests/instance/test_stats.py
+++ b/api/tests/instance/test_stats.py
@@ -4,14 +4,13 @@ from funkwhale_api.instance import stats
 
 
 def test_get_users(mocker):
-    mocker.patch(
-        'funkwhale_api.users.models.User.objects.count', return_value=42)
+    mocker.patch("funkwhale_api.users.models.User.objects.count", return_value=42)
 
     assert stats.get_users() == 42
 
 
 def test_get_music_duration(factories):
-    factories['music.TrackFile'].create_batch(size=5, duration=360)
+    factories["music.TrackFile"].create_batch(size=5, duration=360)
 
     # duration is in hours
     assert stats.get_music_duration() == 0.5
@@ -19,56 +18,48 @@ def test_get_music_duration(factories):
 
 def test_get_listenings(mocker):
     mocker.patch(
-        'funkwhale_api.history.models.Listening.objects.count',
-         return_value=42)
+        "funkwhale_api.history.models.Listening.objects.count", return_value=42
+    )
     assert stats.get_listenings() == 42
 
 
 def test_get_track_favorites(mocker):
     mocker.patch(
-        'funkwhale_api.favorites.models.TrackFavorite.objects.count',
-         return_value=42)
+        "funkwhale_api.favorites.models.TrackFavorite.objects.count", return_value=42
+    )
     assert stats.get_track_favorites() == 42
 
 
 def test_get_tracks(mocker):
-    mocker.patch(
-        'funkwhale_api.music.models.Track.objects.count',
-         return_value=42)
+    mocker.patch("funkwhale_api.music.models.Track.objects.count", return_value=42)
     assert stats.get_tracks() == 42
 
 
 def test_get_albums(mocker):
-    mocker.patch(
-        'funkwhale_api.music.models.Album.objects.count',
-         return_value=42)
+    mocker.patch("funkwhale_api.music.models.Album.objects.count", return_value=42)
     assert stats.get_albums() == 42
 
 
 def test_get_artists(mocker):
-    mocker.patch(
-        'funkwhale_api.music.models.Artist.objects.count',
-         return_value=42)
+    mocker.patch("funkwhale_api.music.models.Artist.objects.count", return_value=42)
     assert stats.get_artists() == 42
 
 
 def test_get(mocker):
     keys = [
-        'users',
-        'tracks',
-        'albums',
-        'artists',
-        'track_favorites',
-        'listenings',
-        'music_duration',
+        "users",
+        "tracks",
+        "albums",
+        "artists",
+        "track_favorites",
+        "listenings",
+        "music_duration",
     ]
     mocks = [
-        mocker.patch.object(stats, 'get_{}'.format(k), return_value=i)
+        mocker.patch.object(stats, "get_{}".format(k), return_value=i)
         for i, k in enumerate(keys)
     ]
 
-    expected = {
-        k: i for i, k in enumerate(keys)
-    }
+    expected = {k: i for i, k in enumerate(keys)}
 
     assert stats.get() == expected
diff --git a/api/tests/instance/test_views.py b/api/tests/instance/test_views.py
index daf54db51c..dc9de2a8c6 100644
--- a/api/tests/instance/test_views.py
+++ b/api/tests/instance/test_views.py
@@ -5,58 +5,53 @@ from django.urls import reverse
 from funkwhale_api.instance import views
 
 
-@pytest.mark.parametrize('view,permissions', [
-    (views.AdminSettings, ['settings']),
-])
+@pytest.mark.parametrize("view,permissions", [(views.AdminSettings, ["settings"])])
 def test_permissions(assert_user_permission, view, permissions):
     assert_user_permission(view, permissions)
 
 
 def test_nodeinfo_endpoint(db, api_client, mocker):
-    payload = {
-        'test': 'test'
-    }
+    payload = {"test": "test"}
     mocked_nodeinfo = mocker.patch(
-        'funkwhale_api.instance.nodeinfo.get', return_value=payload)
-    url = reverse('api:v1:instance:nodeinfo-2.0')
+        "funkwhale_api.instance.nodeinfo.get", return_value=payload
+    )
+    url = reverse("api:v1:instance:nodeinfo-2.0")
     response = api_client.get(url)
-    ct = 'application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8'  # noqa
+    ct = "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8"  # noqa
     assert response.status_code == 200
-    assert response['Content-Type'] == ct
+    assert response["Content-Type"] == ct
     assert response.data == payload
 
 
 def test_nodeinfo_endpoint_disabled(db, api_client, preferences):
-    preferences['instance__nodeinfo_enabled'] = False
-    url = reverse('api:v1:instance:nodeinfo-2.0')
+    preferences["instance__nodeinfo_enabled"] = False
+    url = reverse("api:v1:instance:nodeinfo-2.0")
     response = api_client.get(url)
 
     assert response.status_code == 404
 
 
 def test_settings_only_list_public_settings(db, api_client, preferences):
-    url = reverse('api:v1:instance:settings')
+    url = reverse("api:v1:instance:settings")
     response = api_client.get(url)
 
     for conf in response.data:
-        p = preferences.model.objects.get(
-            section=conf['section'], name=conf['name'])
+        p = preferences.model.objects.get(section=conf["section"], name=conf["name"])
         assert p.preference.show_in_api is True
 
 
 def test_admin_settings_restrict_access(db, logged_in_api_client, preferences):
-    url = reverse('api:v1:instance:admin-settings-list')
+    url = reverse("api:v1:instance:admin-settings-list")
     response = logged_in_api_client.get(url)
 
     assert response.status_code == 403
 
 
-def test_admin_settings_correct_permission(
-        db, logged_in_api_client, preferences):
+def test_admin_settings_correct_permission(db, logged_in_api_client, preferences):
     user = logged_in_api_client.user
     user.permission_settings = True
     user.save()
-    url = reverse('api:v1:instance:admin-settings-list')
+    url = reverse("api:v1:instance:admin-settings-list")
     response = logged_in_api_client.get(url)
 
     assert response.status_code == 200
diff --git a/api/tests/manage/test_serializers.py b/api/tests/manage/test_serializers.py
index 45167722ca..893cfd86e4 100644
--- a/api/tests/manage/test_serializers.py
+++ b/api/tests/manage/test_serializers.py
@@ -2,7 +2,7 @@ from funkwhale_api.manage import serializers
 
 
 def test_manage_track_file_action_delete(factories):
-    tfs = factories['music.TrackFile'](size=5)
+    tfs = factories["music.TrackFile"](size=5)
     s = serializers.ManageTrackFileActionSerializer(queryset=None)
 
     s.handle_delete(tfs.__class__.objects.all())
diff --git a/api/tests/manage/test_views.py b/api/tests/manage/test_views.py
index db2e0980a8..aa8347f763 100644
--- a/api/tests/manage/test_views.py
+++ b/api/tests/manage/test_views.py
@@ -6,21 +6,22 @@ from funkwhale_api.manage import serializers
 from funkwhale_api.manage import views
 
 
-@pytest.mark.parametrize('view,permissions,operator', [
-    (views.ManageTrackFileViewSet, ['library'], 'and'),
-])
+@pytest.mark.parametrize(
+    "view,permissions,operator", [(views.ManageTrackFileViewSet, ["library"], "and")]
+)
 def test_permissions(assert_user_permission, view, permissions, operator):
     assert_user_permission(view, permissions, operator)
 
 
 def test_track_file_view(factories, superuser_api_client):
-    tfs = factories['music.TrackFile'].create_batch(size=5)
-    qs = tfs[0].__class__.objects.order_by('-creation_date')
-    url = reverse('api:v1:manage:library:track-files-list')
+    tfs = factories["music.TrackFile"].create_batch(size=5)
+    qs = tfs[0].__class__.objects.order_by("-creation_date")
+    url = reverse("api:v1:manage:library:track-files-list")
 
-    response = superuser_api_client.get(url, {'sort': '-creation_date'})
+    response = superuser_api_client.get(url, {"sort": "-creation_date"})
     expected = serializers.ManageTrackFileSerializer(
-        qs, many=True, context={'request': response.wsgi_request}).data
+        qs, many=True, context={"request": response.wsgi_request}
+    ).data
 
-    assert response.data['count'] == len(tfs)
-    assert response.data['results'] == expected
+    assert response.data["count"] == len(tfs)
+    assert response.data["results"] == expected
diff --git a/api/tests/music/conftest.py b/api/tests/music/conftest.py
index 4eea8effe1..8108b1a499 100644
--- a/api/tests/music/conftest.py
+++ b/api/tests/music/conftest.py
@@ -1,66 +1,76 @@
 import pytest
 
 
-_artists = {'search': {}, 'get': {}}
+_artists = {"search": {}, "get": {}}
 
-_artists['search']['adhesive_wombat'] = {
-    'artist-list': [
+_artists["search"]["adhesive_wombat"] = {
+    "artist-list": [
         {
-            'type': 'Person',
-            'ext:score': '100',
-            'id': '62c3befb-6366-4585-b256-809472333801',
-            'disambiguation': 'George Shaw',
-            'gender': 'male',
-            'area': {'sort-name': 'Raleigh', 'id': '3f8828b9-ba93-4604-9b92-1f616fa1abd1', 'name': 'Raleigh'},
-            'sort-name': 'Wombat, Adhesive',
-            'life-span': {'ended': 'false'},
-            'name': 'Adhesive Wombat'
+            "type": "Person",
+            "ext:score": "100",
+            "id": "62c3befb-6366-4585-b256-809472333801",
+            "disambiguation": "George Shaw",
+            "gender": "male",
+            "area": {
+                "sort-name": "Raleigh",
+                "id": "3f8828b9-ba93-4604-9b92-1f616fa1abd1",
+                "name": "Raleigh",
+            },
+            "sort-name": "Wombat, Adhesive",
+            "life-span": {"ended": "false"},
+            "name": "Adhesive Wombat",
         },
         {
-            'country': 'SE',
-            'type': 'Group',
-            'ext:score': '42',
-            'id': '61b34e69-7573-4208-bc89-7061bca5a8fc',
-            'area': {'sort-name': 'Sweden', 'id': '23d10872-f5ae-3f0c-bf55-332788a16ecb', 'name': 'Sweden'},
-            'sort-name': 'Adhesive',
-            'life-span': {'end': '2002-07-12', 'begin': '1994', 'ended': 'true'},
-            'name': 'Adhesive',
-            'begin-area': {
-                'sort-name': 'Katrineholm',
-                'id': '02390d96-b5a3-4282-a38f-e64a95d08b7f',
-                'name': 'Katrineholm'
+            "country": "SE",
+            "type": "Group",
+            "ext:score": "42",
+            "id": "61b34e69-7573-4208-bc89-7061bca5a8fc",
+            "area": {
+                "sort-name": "Sweden",
+                "id": "23d10872-f5ae-3f0c-bf55-332788a16ecb",
+                "name": "Sweden",
+            },
+            "sort-name": "Adhesive",
+            "life-span": {"end": "2002-07-12", "begin": "1994", "ended": "true"},
+            "name": "Adhesive",
+            "begin-area": {
+                "sort-name": "Katrineholm",
+                "id": "02390d96-b5a3-4282-a38f-e64a95d08b7f",
+                "name": "Katrineholm",
             },
         },
     ]
 }
-_artists['get']['adhesive_wombat'] = {'artist': _artists['search']['adhesive_wombat']['artist-list'][0]}
+_artists["get"]["adhesive_wombat"] = {
+    "artist": _artists["search"]["adhesive_wombat"]["artist-list"][0]
+}
 
-_artists['get']['soad'] = {
-    'artist': {
-        'country': 'US',
-        'isni-list': ['0000000121055332'],
-        'type': 'Group',
-        'area': {
-            'iso-3166-1-code-list': ['US'],
-            'sort-name': 'United States',
-            'id': '489ce91b-6658-3307-9877-795b68554c98',
-            'name': 'United States'
+_artists["get"]["soad"] = {
+    "artist": {
+        "country": "US",
+        "isni-list": ["0000000121055332"],
+        "type": "Group",
+        "area": {
+            "iso-3166-1-code-list": ["US"],
+            "sort-name": "United States",
+            "id": "489ce91b-6658-3307-9877-795b68554c98",
+            "name": "United States",
         },
-        'begin-area': {
-            'sort-name': 'Glendale',
-            'id': '6db2e45d-d7f3-43da-ac0b-7ba5ca627373',
-            'name': 'Glendale'
+        "begin-area": {
+            "sort-name": "Glendale",
+            "id": "6db2e45d-d7f3-43da-ac0b-7ba5ca627373",
+            "name": "Glendale",
         },
-        'id': 'cc0b7089-c08d-4c10-b6b0-873582c17fd6',
-        'life-span': {'begin': '1994'},
-        'sort-name': 'System of a Down',
-        'name': 'System of a Down'
+        "id": "cc0b7089-c08d-4c10-b6b0-873582c17fd6",
+        "life-span": {"begin": "1994"},
+        "sort-name": "System of a Down",
+        "name": "System of a Down",
     }
 }
 
-_albums = {'search': {}, 'get': {}, 'get_with_includes': {}}
-_albums['search']['hypnotize'] = {
-    'release-list': [
+_albums = {"search": {}, "get": {}, "get_with_includes": {}}
+_albums["search"]["hypnotize"] = {
+    "release-list": [
         {
             "artist-credit": [
                 {
@@ -69,22 +79,22 @@ _albums['search']['hypnotize'] = {
                             {
                                 "alias": "SoaD",
                                 "sort-name": "SoaD",
-                                "type": "Search hint"
+                                "type": "Search hint",
                             },
                             {
                                 "alias": "S.O.A.D.",
                                 "sort-name": "S.O.A.D.",
-                                "type": "Search hint"
+                                "type": "Search hint",
                             },
                             {
                                 "alias": "System Of Down",
                                 "sort-name": "System Of Down",
-                                "type": "Search hint"
-                            }
+                                "type": "Search hint",
+                            },
                         ],
                         "id": "cc0b7089-c08d-4c10-b6b0-873582c17fd6",
                         "name": "System of a Down",
-                        "sort-name": "System of a Down"
+                        "sort-name": "System of a Down",
                     }
                 }
             ],
@@ -99,16 +109,16 @@ _albums['search']['hypnotize'] = {
                     "catalog-number": "8-2796-93871-2",
                     "label": {
                         "id": "f5be9cfe-e1af-405c-a074-caeaed6797c0",
-                        "name": "American Recordings"
-                    }
+                        "name": "American Recordings",
+                    },
                 },
                 {
                     "catalog-number": "D162990",
                     "label": {
                         "id": "9a7d39a4-a887-40f3-a645-a9a136d1f13f",
-                        "name": "BMG Direct Marketing, Inc."
-                    }
-                }
+                        "name": "BMG Direct Marketing, Inc.",
+                    },
+                },
             ],
             "medium-count": 1,
             "medium-list": [
@@ -117,7 +127,7 @@ _albums['search']['hypnotize'] = {
                     "disc-list": [],
                     "format": "CD",
                     "track-count": 12,
-                    "track-list": []
+                    "track-list": [],
                 }
             ],
             "medium-track-count": 12,
@@ -126,26 +136,21 @@ _albums['search']['hypnotize'] = {
                 {
                     "area": {
                         "id": "489ce91b-6658-3307-9877-795b68554c98",
-                        "iso-3166-1-code-list": [
-                            "US"
-                        ],
+                        "iso-3166-1-code-list": ["US"],
                         "name": "United States",
-                        "sort-name": "United States"
+                        "sort-name": "United States",
                     },
-                    "date": "2005"
+                    "date": "2005",
                 }
             ],
             "release-group": {
                 "id": "72035143-d6ec-308b-8ee5-070b8703902a",
                 "primary-type": "Album",
-                "type": "Album"
+                "type": "Album",
             },
             "status": "Official",
-            "text-representation": {
-                "language": "eng",
-                "script": "Latn"
-            },
-            "title": "Hypnotize"
+            "text-representation": {"language": "eng", "script": "Latn"},
+            "title": "Hypnotize",
         },
         {
             "artist-credit": [
@@ -155,22 +160,22 @@ _albums['search']['hypnotize'] = {
                             {
                                 "alias": "SoaD",
                                 "sort-name": "SoaD",
-                                "type": "Search hint"
+                                "type": "Search hint",
                             },
                             {
                                 "alias": "S.O.A.D.",
                                 "sort-name": "S.O.A.D.",
-                                "type": "Search hint"
+                                "type": "Search hint",
                             },
                             {
                                 "alias": "System Of Down",
                                 "sort-name": "System Of Down",
-                                "type": "Search hint"
-                            }
+                                "type": "Search hint",
+                            },
                         ],
                         "id": "cc0b7089-c08d-4c10-b6b0-873582c17fd6",
                         "name": "System of a Down",
-                        "sort-name": "System of a Down"
+                        "sort-name": "System of a Down",
                     }
                 }
             ],
@@ -188,7 +193,7 @@ _albums['search']['hypnotize'] = {
                     "disc-list": [],
                     "format": "Vinyl",
                     "track-count": 12,
-                    "track-list": []
+                    "track-list": [],
                 }
             ],
             "medium-track-count": 12,
@@ -196,167 +201,233 @@ _albums['search']['hypnotize'] = {
                 {
                     "area": {
                         "id": "489ce91b-6658-3307-9877-795b68554c98",
-                        "iso-3166-1-code-list": [
-                            "US"
-                        ],
+                        "iso-3166-1-code-list": ["US"],
                         "name": "United States",
-                        "sort-name": "United States"
+                        "sort-name": "United States",
                     },
-                    "date": "2005-12-20"
+                    "date": "2005-12-20",
                 }
             ],
             "release-group": {
                 "id": "72035143-d6ec-308b-8ee5-070b8703902a",
                 "primary-type": "Album",
-                "type": "Album"
+                "type": "Album",
             },
             "status": "Official",
-            "text-representation": {
-                "language": "eng",
-                "script": "Latn"
-            },
-            "title": "Hypnotize"
+            "text-representation": {"language": "eng", "script": "Latn"},
+            "title": "Hypnotize",
         },
     ]
 }
-_albums['get']['hypnotize'] = {'release': _albums['search']['hypnotize']['release-list'][0]}
-_albums['get_with_includes']['hypnotize'] = {
-  'release': {
-    'artist-credit': [
-        {'artist': {'id': 'cc0b7089-c08d-4c10-b6b0-873582c17fd6',
-            'name': 'System of a Down',
-            'sort-name': 'System of a Down'}}],
-  'artist-credit-phrase': 'System of a Down',
-  'barcode': '',
-  'country': 'US',
-  'cover-art-archive': {'artwork': 'true',
-   'back': 'false',
-   'count': '1',
-   'front': 'true'},
-  'date': '2005',
-  'id': '47ae093f-1607-49a3-be11-a15d335ccc94',
-  'medium-count': 1,
-  'medium-list': [{'format': 'CD',
-    'position': '1',
-    'track-count': 12,
-    'track-list': [{'id': '59f5cf9a-75b2-3aa3-abda-6807a87107b3',
-      'length': '186000',
-      'number': '1',
-      'position': '1',
-      'recording': {'id': '76d03fc5-758c-48d0-a354-a67de086cc68',
-       'length': '186000',
-       'title': 'Attack'},
-      'track_or_recording_length': '186000'},
-     {'id': '3aaa28c1-12b1-3c2a-b90a-82e09e355608',
-      'length': '239000',
-      'number': '2',
-      'position': '2',
-      'recording': {'id': '327543b0-9193-48c5-83c9-01c7b36c8c0a',
-       'length': '239000',
-       'title': 'Dreaming'},
-      'track_or_recording_length': '239000'},
-     {'id': 'a34fef19-e637-3436-b7eb-276ff2814d6f',
-      'length': '147000',
-      'number': '3',
-      'position': '3',
-      'recording': {'id': '6e27866c-07a1-425d-bb4f-9d9e728db344',
-       'length': '147000',
-       'title': 'Kill Rock ’n Roll'},
-      'track_or_recording_length': '147000'},
-     {'id': '72a4e5c0-c150-3ba1-9ceb-3ab82648af25',
-      'length': '189000',
-      'number': '4',
-      'position': '4',
-      'recording': {'id': '7ff8a67d-c8e2-4b3a-a045-7ad3561d0605',
-       'length': '189000',
-       'title': 'Hypnotize'},
-      'track_or_recording_length': '189000'},
-     {'id': 'a748fa6e-b3b7-3b22-89fb-a038ec92ac32',
-      'length': '178000',
-      'number': '5',
-      'position': '5',
-      'recording': {'id': '19b6eb6a-0e76-4ef7-b63f-959339dbd5d2',
-       'length': '178000',
-       'title': 'Stealing Society'},
-      'track_or_recording_length': '178000'},
-     {'id': '5c5a8d4e-e21a-317e-a719-6e2dbdefa5d2',
-      'length': '216000',
-      'number': '6',
-      'position': '6',
-      'recording': {'id': 'c3c2afe1-ee9a-47cb-b3c6-ff8100bc19d5',
-       'length': '216000',
-       'title': 'Tentative'},
-      'track_or_recording_length': '216000'},
-     {'id': '265718ba-787f-3193-947b-3b6fa69ffe96',
-      'length': '175000',
-      'number': '7',
-      'position': '7',
-      'recording': {'id': '96f804e1-f600-4faa-95a6-ce597e7db120',
-       'length': '175000',
-       'title': 'U‐Fig'},
-      'title': 'U-Fig',
-      'track_or_recording_length': '175000'},
-     {'id': 'cdcf8572-3060-31ca-a72c-1ded81ca1f7a',
-      'length': '328000',
-      'number': '8',
-      'position': '8',
-      'recording': {'id': '26ba38f0-b26b-48b7-8e77-226b22a55f79',
-       'length': '328000',
-       'title': 'Holy Mountains'},
-      'track_or_recording_length': '328000'},
-     {'id': 'f9f00cb0-5635-3217-a2a0-bd61917eb0df',
-      'length': '171000',
-      'number': '9',
-      'position': '9',
-      'recording': {'id': '039f3379-3a69-4e75-a882-df1c4e1608aa',
-       'length': '171000',
-       'title': 'Vicinity of Obscenity'},
-      'track_or_recording_length': '171000'},
-     {'id': 'cdd45914-6741-353e-bbb5-d281048ff24f',
-      'length': '164000',
-      'number': '10',
-      'position': '10',
-      'recording': {'id': 'c24d541a-a9a8-4a22-84c6-5e6419459cf8',
-       'length': '164000',
-       'title': 'She’s Like Heroin'},
-      'track_or_recording_length': '164000'},
-     {'id': 'cfcf12ac-6831-3dd6-a2eb-9d0bfeee3f6d',
-      'length': '167000',
-      'number': '11',
-      'position': '11',
-      'recording': {'id': '0aff4799-849f-4f83-84f4-22cabbba2378',
-       'length': '167000',
-       'title': 'Lonely Day'},
-      'track_or_recording_length': '167000'},
-     {'id': '7e38bb38-ff62-3e41-a670-b7d77f578a1f',
-      'length': '220000',
-      'number': '12',
-      'position': '12',
-      'recording': {'id': 'e1b4d90f-2f44-4fe6-a826-362d4e3d9b88',
-       'length': '220000',
-       'title': 'Soldier Side'},
-      'track_or_recording_length': '220000'}]}],
-  'packaging': 'Digipak',
-  'quality': 'normal',
-  'release-event-count': 1,
-  'release-event-list': [{'area': {'id': '489ce91b-6658-3307-9877-795b68554c98',
-     'iso-3166-1-code-list': ['US'],
-     'name': 'United States',
-     'sort-name': 'United States'},
-    'date': '2005'}],
-  'status': 'Official',
-  'text-representation': {'language': 'eng', 'script': 'Latn'},
-  'title': 'Hypnotize'}}
+_albums["get"]["hypnotize"] = {
+    "release": _albums["search"]["hypnotize"]["release-list"][0]
+}
+_albums["get_with_includes"]["hypnotize"] = {
+    "release": {
+        "artist-credit": [
+            {
+                "artist": {
+                    "id": "cc0b7089-c08d-4c10-b6b0-873582c17fd6",
+                    "name": "System of a Down",
+                    "sort-name": "System of a Down",
+                }
+            }
+        ],
+        "artist-credit-phrase": "System of a Down",
+        "barcode": "",
+        "country": "US",
+        "cover-art-archive": {
+            "artwork": "true",
+            "back": "false",
+            "count": "1",
+            "front": "true",
+        },
+        "date": "2005",
+        "id": "47ae093f-1607-49a3-be11-a15d335ccc94",
+        "medium-count": 1,
+        "medium-list": [
+            {
+                "format": "CD",
+                "position": "1",
+                "track-count": 12,
+                "track-list": [
+                    {
+                        "id": "59f5cf9a-75b2-3aa3-abda-6807a87107b3",
+                        "length": "186000",
+                        "number": "1",
+                        "position": "1",
+                        "recording": {
+                            "id": "76d03fc5-758c-48d0-a354-a67de086cc68",
+                            "length": "186000",
+                            "title": "Attack",
+                        },
+                        "track_or_recording_length": "186000",
+                    },
+                    {
+                        "id": "3aaa28c1-12b1-3c2a-b90a-82e09e355608",
+                        "length": "239000",
+                        "number": "2",
+                        "position": "2",
+                        "recording": {
+                            "id": "327543b0-9193-48c5-83c9-01c7b36c8c0a",
+                            "length": "239000",
+                            "title": "Dreaming",
+                        },
+                        "track_or_recording_length": "239000",
+                    },
+                    {
+                        "id": "a34fef19-e637-3436-b7eb-276ff2814d6f",
+                        "length": "147000",
+                        "number": "3",
+                        "position": "3",
+                        "recording": {
+                            "id": "6e27866c-07a1-425d-bb4f-9d9e728db344",
+                            "length": "147000",
+                            "title": "Kill Rock ’n Roll",
+                        },
+                        "track_or_recording_length": "147000",
+                    },
+                    {
+                        "id": "72a4e5c0-c150-3ba1-9ceb-3ab82648af25",
+                        "length": "189000",
+                        "number": "4",
+                        "position": "4",
+                        "recording": {
+                            "id": "7ff8a67d-c8e2-4b3a-a045-7ad3561d0605",
+                            "length": "189000",
+                            "title": "Hypnotize",
+                        },
+                        "track_or_recording_length": "189000",
+                    },
+                    {
+                        "id": "a748fa6e-b3b7-3b22-89fb-a038ec92ac32",
+                        "length": "178000",
+                        "number": "5",
+                        "position": "5",
+                        "recording": {
+                            "id": "19b6eb6a-0e76-4ef7-b63f-959339dbd5d2",
+                            "length": "178000",
+                            "title": "Stealing Society",
+                        },
+                        "track_or_recording_length": "178000",
+                    },
+                    {
+                        "id": "5c5a8d4e-e21a-317e-a719-6e2dbdefa5d2",
+                        "length": "216000",
+                        "number": "6",
+                        "position": "6",
+                        "recording": {
+                            "id": "c3c2afe1-ee9a-47cb-b3c6-ff8100bc19d5",
+                            "length": "216000",
+                            "title": "Tentative",
+                        },
+                        "track_or_recording_length": "216000",
+                    },
+                    {
+                        "id": "265718ba-787f-3193-947b-3b6fa69ffe96",
+                        "length": "175000",
+                        "number": "7",
+                        "position": "7",
+                        "recording": {
+                            "id": "96f804e1-f600-4faa-95a6-ce597e7db120",
+                            "length": "175000",
+                            "title": "U‐Fig",
+                        },
+                        "title": "U-Fig",
+                        "track_or_recording_length": "175000",
+                    },
+                    {
+                        "id": "cdcf8572-3060-31ca-a72c-1ded81ca1f7a",
+                        "length": "328000",
+                        "number": "8",
+                        "position": "8",
+                        "recording": {
+                            "id": "26ba38f0-b26b-48b7-8e77-226b22a55f79",
+                            "length": "328000",
+                            "title": "Holy Mountains",
+                        },
+                        "track_or_recording_length": "328000",
+                    },
+                    {
+                        "id": "f9f00cb0-5635-3217-a2a0-bd61917eb0df",
+                        "length": "171000",
+                        "number": "9",
+                        "position": "9",
+                        "recording": {
+                            "id": "039f3379-3a69-4e75-a882-df1c4e1608aa",
+                            "length": "171000",
+                            "title": "Vicinity of Obscenity",
+                        },
+                        "track_or_recording_length": "171000",
+                    },
+                    {
+                        "id": "cdd45914-6741-353e-bbb5-d281048ff24f",
+                        "length": "164000",
+                        "number": "10",
+                        "position": "10",
+                        "recording": {
+                            "id": "c24d541a-a9a8-4a22-84c6-5e6419459cf8",
+                            "length": "164000",
+                            "title": "She’s Like Heroin",
+                        },
+                        "track_or_recording_length": "164000",
+                    },
+                    {
+                        "id": "cfcf12ac-6831-3dd6-a2eb-9d0bfeee3f6d",
+                        "length": "167000",
+                        "number": "11",
+                        "position": "11",
+                        "recording": {
+                            "id": "0aff4799-849f-4f83-84f4-22cabbba2378",
+                            "length": "167000",
+                            "title": "Lonely Day",
+                        },
+                        "track_or_recording_length": "167000",
+                    },
+                    {
+                        "id": "7e38bb38-ff62-3e41-a670-b7d77f578a1f",
+                        "length": "220000",
+                        "number": "12",
+                        "position": "12",
+                        "recording": {
+                            "id": "e1b4d90f-2f44-4fe6-a826-362d4e3d9b88",
+                            "length": "220000",
+                            "title": "Soldier Side",
+                        },
+                        "track_or_recording_length": "220000",
+                    },
+                ],
+            }
+        ],
+        "packaging": "Digipak",
+        "quality": "normal",
+        "release-event-count": 1,
+        "release-event-list": [
+            {
+                "area": {
+                    "id": "489ce91b-6658-3307-9877-795b68554c98",
+                    "iso-3166-1-code-list": ["US"],
+                    "name": "United States",
+                    "sort-name": "United States",
+                },
+                "date": "2005",
+            }
+        ],
+        "status": "Official",
+        "text-representation": {"language": "eng", "script": "Latn"},
+        "title": "Hypnotize",
+    }
+}
 
-_albums['get']['marsupial'] = {
-    'release': {
+_albums["get"]["marsupial"] = {
+    "release": {
         "artist-credit": [
             {
                 "artist": {
                     "disambiguation": "George Shaw",
                     "id": "62c3befb-6366-4585-b256-809472333801",
                     "name": "Adhesive Wombat",
-                    "sort-name": "Wombat, Adhesive"
+                    "sort-name": "Wombat, Adhesive",
                 }
             }
         ],
@@ -366,7 +437,7 @@ _albums['get']['marsupial'] = {
             "artwork": "true",
             "back": "false",
             "count": "1",
-            "front": "true"
+            "front": "true",
         },
         "date": "2013-06-05",
         "id": "a50d2a81-2a50-484d-9cb4-b9f6833f583e",
@@ -377,28 +448,23 @@ _albums['get']['marsupial'] = {
             {
                 "area": {
                     "id": "525d4e18-3d00-31b9-a58b-a146a916de8f",
-                    "iso-3166-1-code-list": [
-                        "XW"
-                    ],
+                    "iso-3166-1-code-list": ["XW"],
                     "name": "[Worldwide]",
-                    "sort-name": "[Worldwide]"
+                    "sort-name": "[Worldwide]",
                 },
-                "date": "2013-06-05"
+                "date": "2013-06-05",
             }
         ],
         "status": "Official",
-        "text-representation": {
-            "language": "eng",
-            "script": "Latn"
-        },
-        "title": "Marsupial Madness"
+        "text-representation": {"language": "eng", "script": "Latn"},
+        "title": "Marsupial Madness",
     }
 }
 
-_tracks = {'search': {}, 'get': {}}
+_tracks = {"search": {}, "get": {}}
 
-_tracks['search']['8bitadventures'] = {
-    'recording-list': [
+_tracks["search"]["8bitadventures"] = {
+    "recording-list": [
         {
             "artist-credit": [
                 {
@@ -406,7 +472,7 @@ _tracks['search']['8bitadventures'] = {
                         "disambiguation": "George Shaw",
                         "id": "62c3befb-6366-4585-b256-809472333801",
                         "name": "Adhesive Wombat",
-                        "sort-name": "Wombat, Adhesive"
+                        "sort-name": "Wombat, Adhesive",
                     }
                 }
             ],
@@ -430,9 +496,9 @@ _tracks['search']['8bitadventures'] = {
                                     "length": "271000",
                                     "number": "1",
                                     "title": "8-Bit Adventure",
-                                    "track_or_recording_length": "271000"
+                                    "track_or_recording_length": "271000",
                                 }
-                            ]
+                            ],
                         }
                     ],
                     "medium-track-count": 11,
@@ -440,70 +506,85 @@ _tracks['search']['8bitadventures'] = {
                         {
                             "area": {
                                 "id": "525d4e18-3d00-31b9-a58b-a146a916de8f",
-                                "iso-3166-1-code-list": [
-                                    "XW"
-                                ],
+                                "iso-3166-1-code-list": ["XW"],
                                 "name": "[Worldwide]",
-                                "sort-name": "[Worldwide]"
+                                "sort-name": "[Worldwide]",
                             },
-                            "date": "2013-06-05"
+                            "date": "2013-06-05",
                         }
                     ],
                     "release-group": {
                         "id": "447b4979-2178-405c-bfe6-46bf0b09e6c7",
                         "primary-type": "Album",
-                        "type": "Album"
+                        "type": "Album",
                     },
                     "status": "Official",
-                    "title": "Marsupial Madness"
+                    "title": "Marsupial Madness",
                 }
             ],
             "title": "8-Bit Adventure",
             "tag-list": [
-                {
-                    "count": "2",
-                    "name": "techno"
-                },
-                {
-                    "count": "2",
-                    "name": "good-music"
-                },
+                {"count": "2", "name": "techno"},
+                {"count": "2", "name": "good-music"},
             ],
-        },
+        }
     ]
 }
 
-_tracks['get']['8bitadventures'] = {'recording': _tracks['search']['8bitadventures']['recording-list'][0]}
-_tracks['get']['chop_suey'] = {
-    'recording': {
-        'id': '46c7368a-013a-47b6-97cc-e55e7ab25213',
-        'length': '210240',
-        'title': 'Chop Suey!',
-        'work-relation-list': [{'target': 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5',
-        'type': 'performance',
-        'type-id': 'a3005666-a872-32c3-ad06-98af558e99b0',
-        'work': {'id': 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5',
-            'language': 'eng',
-            'title': 'Chop Suey!'}}]}}
+_tracks["get"]["8bitadventures"] = {
+    "recording": _tracks["search"]["8bitadventures"]["recording-list"][0]
+}
+_tracks["get"]["chop_suey"] = {
+    "recording": {
+        "id": "46c7368a-013a-47b6-97cc-e55e7ab25213",
+        "length": "210240",
+        "title": "Chop Suey!",
+        "work-relation-list": [
+            {
+                "target": "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5",
+                "type": "performance",
+                "type-id": "a3005666-a872-32c3-ad06-98af558e99b0",
+                "work": {
+                    "id": "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5",
+                    "language": "eng",
+                    "title": "Chop Suey!",
+                },
+            }
+        ],
+    }
+}
 
-_works = {'search': {}, 'get': {}}
-_works['get']['chop_suey'] = {'work': {'id': 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5',
-  'language': 'eng',
-  'recording-relation-list': [{'direction': 'backward',
-    'recording': {'disambiguation': 'edit',
-     'id': '07ca77cf-f513-4e9c-b190-d7e24bbad448',
-     'length': '170893',
-     'title': 'Chop Suey!'},
-    'target': '07ca77cf-f513-4e9c-b190-d7e24bbad448',
-    'type': 'performance',
-    'type-id': 'a3005666-a872-32c3-ad06-98af558e99b0'},
-  ],
-  'title': 'Chop Suey!',
-  'type': 'Song',
-  'url-relation-list': [{'direction': 'backward',
-    'target': 'http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!',
-    'type': 'lyrics',
-    'type-id': 'e38e65aa-75e0-42ba-ace0-072aeb91a538'}]}}
+_works = {"search": {}, "get": {}}
+_works["get"]["chop_suey"] = {
+    "work": {
+        "id": "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5",
+        "language": "eng",
+        "recording-relation-list": [
+            {
+                "direction": "backward",
+                "recording": {
+                    "disambiguation": "edit",
+                    "id": "07ca77cf-f513-4e9c-b190-d7e24bbad448",
+                    "length": "170893",
+                    "title": "Chop Suey!",
+                },
+                "target": "07ca77cf-f513-4e9c-b190-d7e24bbad448",
+                "type": "performance",
+                "type-id": "a3005666-a872-32c3-ad06-98af558e99b0",
+            }
+        ],
+        "title": "Chop Suey!",
+        "type": "Song",
+        "url-relation-list": [
+            {
+                "direction": "backward",
+                "target": "http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!",
+                "type": "lyrics",
+                "type-id": "e38e65aa-75e0-42ba-ace0-072aeb91a538",
+            }
+        ],
+    }
+}
 
 
 @pytest.fixture()
@@ -570,4 +651,4 @@ def binary_cover():
     """
     Return an album cover image in form of a binary string
     """
-    return b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x02\x01\x00H\x00H\x00\x00\xff\xed\x08\xaePhotoshop 3.0\x008BIM\x03\xe9\x00\x00\x00\x00\x00x\x00\x03\x00\x00\x00H\x00H\x00\x00\x00\x00\x02\xd8\x02(\xff\xe1\xff\xe2\x02\xf9\x02F\x03G\x05(\x03\xfc\x00\x02\x00\x00\x00H\x00H\x00\x00\x00\x00\x02\xd8\x02(\x00\x01\x00\x00\x00d\x00\x00\x00\x01\x00\x03\x03\x03\x00\x00\x00\x01\'\x0f\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x08\x00\x19\x01\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008BIM\x03\xed\x00\x00\x00\x00\x00\x10\x00H\x00\x00\x00\x01\x00\x01\x00H\x00\x00\x00\x01\x00\x018BIM\x03\xf3\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x008BIM\x04\n\x00\x00\x00\x00\x00\x01\x00\x008BIM\'\x10\x00\x00\x00\x00\x00\n\x00\x01\x00\x00\x00\x00\x00\x00\x00\x028BIM\x03\xf5\x00\x00\x00\x00\x00H\x00/ff\x00\x01\x00lff\x00\x06\x00\x00\x00\x00\x00\x01\x00/ff\x00\x01\x00\xa1\x99\x9a\x00\x06\x00\x00\x00\x00\x00\x01\x002\x00\x00\x00\x01\x00Z\x00\x00\x00\x06\x00\x00\x00\x00\x00\x01\x005\x00\x00\x00\x01\x00-\x00\x00\x00\x06\x00\x00\x00\x00\x00\x018BIM\x03\xf8\x00\x00\x00\x00\x00p\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x008BIM\x04\x00\x00\x00\x00\x00\x00\x02\x00\x018BIM\x04\x02\x00\x00\x00\x00\x00\x04\x00\x00\x00\x008BIM\x04\x08\x00\x00\x00\x00\x00\x10\x00\x00\x00\x01\x00\x00\x02@\x00\x00\x02@\x00\x00\x00\x008BIM\x04\t\x00\x00\x00\x00\x06\x9b\x00\x00\x00\x01\x00\x00\x00\x80\x00\x00\x00\x80\x00\x00\x01\x80\x00\x00\xc0\x00\x00\x00\x06\x7f\x00\x18\x00\x01\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x02\x01\x00H\x00H\x00\x00\xff\xfe\x00\'File written by Adobe Photoshop\xa8 4.0\x00\xff\xee\x00\x0eAdobe\x00d\x80\x00\x00\x00\x01\xff\xdb\x00\x84\x00\x0c\x08\x08\x08\t\x08\x0c\t\t\x0c\x11\x0b\n\x0b\x11\x15\x0f\x0c\x0c\x0f\x15\x18\x13\x13\x15\x13\x13\x18\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x01\r\x0b\x0b\r\x0e\r\x10\x0e\x0e\x10\x14\x0e\x0e\x0e\x14\x14\x0e\x0e\x0e\x0e\x14\x11\x0c\x0c\x0c\x0c\x0c\x11\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\xff\xc0\x00\x11\x08\x00\x80\x00\x80\x03\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xdd\x00\x04\x00\x08\xff\xc4\x01?\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x01\x02\x04\x05\x06\x07\x08\t\n\x0b\x01\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x10\x00\x01\x04\x01\x03\x02\x04\x02\x05\x07\x06\x08\x05\x03\x0c3\x01\x00\x02\x11\x03\x04!\x121\x05AQa\x13"q\x812\x06\x14\x91\xa1\xb1B#$\x15R\xc1b34r\x82\xd1C\x07%\x92S\xf0\xe1\xf1cs5\x16\xa2\xb2\x83&D\x93TdE\xc2\xa3t6\x17\xd2U\xe2e\xf2\xb3\x84\xc3\xd3u\xe3\xf3F\'\x94\xa4\x85\xb4\x95\xc4\xd4\xe4\xf4\xa5\xb5\xc5\xd5\xe5\xf5Vfv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf67GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf7\x11\x00\x02\x02\x01\x02\x04\x04\x03\x04\x05\x06\x07\x07\x06\x055\x01\x00\x02\x11\x03!1\x12\x04AQaq"\x13\x052\x81\x91\x14\xa1\xb1B#\xc1R\xd1\xf03$b\xe1r\x82\x92CS\x15cs4\xf1%\x06\x16\xa2\xb2\x83\x07&5\xc2\xd2D\x93T\xa3\x17dEU6te\xe2\xf2\xb3\x84\xc3\xd3u\xe3\xf3F\x94\xa4\x85\xb4\x95\xc4\xd4\xe4\xf4\xa5\xb5\xc5\xd5\xe5\xf5Vfv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf6\'7GWgw\x87\x97\xa7\xb7\xc7\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\xf5T\x92I%)$\x92IJI$\x92R\x92I$\x94\xa4\x92I%)$\x92IJI$\x92R\x92I$\x94\xff\x00\xff\xd0\xf5T\x92I%)$\x92IJI%\xe7\xff\x00Z\x7f\xc6\xbf\xfc\xde\xeb\xb9]\x1f\xf6_\xda~\xcd\xe9\xfe\x9b\xed\x1e\x9e\xefR\xba\xef\xfeo\xec\xf6\xed\xdb\xea\xec\xfeq%>\x80\x92\xf2\xaf\xfc}?\xf3I\xff\x00\xb3_\xfb\xe8\x97\xfe>\x9f\xf9\xa4\xff\x00\xd9\xaf\xfd\xf4IO\xaa\xa4\xbc\xab\xff\x00\x1fO\xfc\xd2\x7f\xec\xd7\xfe\xfa%\xff\x00\x8f\xa7\xfei?\xf6k\xff\x00}\x12S\xea\xa9.+\xeaW\xf8\xc8\xff\x00\x9d}V\xde\x9d\xfb;\xec~\x96;\xb2=O[\xd5\x9d\xaf\xaa\xad\x9b=\n\x7f\xd3}-\xeb\xb5IJI$\x92R\x92I$\x94\xff\x00\xff\xd1\xf5T\x92I%)$\x97\x9f\xff\x00\x8d\x7f\xad=w\xea\xf7\xec\xbf\xd8\xf9_f\xfbO\xda=o\xd1\xd7f\xefO\xec\xfe\x9f\xf3\xf5\xdb\xb7o\xabg\xd0IO\xa0/\x9f\xff\x00\xc6\x97\xfe.\xfa\x9f\xfdc\xff\x00m\xf1\xd2\xff\x00\xc7K\xeb\xdf\xfeY\xff\x00\xe0\x18\xff\x00\xfb\xce\xb9\xfe\xa9\xd53\xfa\xbe}\xbdG\xa8\xdb\xeb\xe5\xdf\xb7\xd4\xb3kY;\x1a\xda\x99\xec\xa9\xac\xaf\xf9\xb63\xf3\x12SU$\x92IJI$\x92S\xdf\xff\x00\x89O\xfcUe\x7f\xe1\x0b?\xf3\xf6*\xf6\xb5\xf3/D\xeb\xfd[\xa0\xe5?3\xa4\xdf\xf6l\x8b+59\xfb\x18\xf9a-\xb1\xcd\xdb{-g\xd3\xa9\x8bk\xff\x00\x1d/\xaf\x7f\xf9g\xff\x00\x80c\xff\x00\xef:J~\x80Iq\xff\x00\xe2\xbf\xaf\xf5n\xbd\xd023:\xb5\xff\x00i\xc8\xaf-\xf55\xfb\x18\xc8`\xae\x8b\x1a\xdd\xb42\xa6};^\xbb\x04\x94\xa4\x92I%?\xff\xd2\xf5T\x92I%)yW\xf8\xf4\xff\x00\xbcO\xfd\n\xff\x00\xddE\xea\xab\xca\xbf\xc7\xa7\xfd\xe2\x7f\xe8W\xfe\xea$\xa7\xca\x92I$\x94\xa4\x92I%)$\x92IJI$\x92S\xed_\xe2S\xff\x00\x12\xb9_\xf8~\xcf\xfc\xf3\x8a\xbd\x01y\xff\x00\xf8\x94\xff\x00\xc4\xaeW\xfe\x1f\xb3\xff\x00<\xe2\xaf@IJI$\x92S\xff\xd3\xf5T\x92I%)yW\xf8\xf4\xff\x00\xbcO\xfd\n\xff\x00\xddE\xea\xab\xca\xbf\xc7\xa7\xfd\xe2\x7f\xe8W\xfe\xea$\xa7\xca\x92I$\x94\xa4\x92I%)$\x92IJI$\x92S\xed_\xe2S\xff\x00\x12\xb9_\xf8~\xcf\xfc\xf3\x8a\xbd\x01y\xff\x00\xf8\x94\xff\x00\xc4\xaeW\xfe\x1f\xb3\xff\x00<\xe2\xaf@IJI$\x92S\xff\xd4\xf5T\x92I%)q_\xe3#\xeaWU\xfa\xd7\xfb;\xf6u\xb8\xf5}\x8f\xd6\xf5>\xd0\xe7\xb6}_Cf\xcfJ\xab\xbf\xd0\xbfr\xedRIO\x8a\x7f\xe3)\xf5\xab\xfe\xe5`\x7f\xdb\x97\x7f\xef*\xe4:\xff\x00D\xca\xe8=Z\xfe\x93\x98\xfa\xec\xc8\xc6\xd9\xbd\xd5\x12Xw\xb1\x97\xb7k\xacmO\xfa\x16\xfe\xe2\xfai|\xff\x00\xfe4\xbf\xf1w\xd4\xff\x00\xeb\x1f\xfbo\x8e\x92\x9eU$\x92IJI$\x92S\xb1\xf5_\xea\xbfP\xfa\xd1\xd4,\xc0\xc0\xb2\x9a\xad\xaa\x93{\x9dys[\xb5\xae\xae\xa2\x01\xaa\xbb\x9d\xbfu\xcd\xfc\xd5\xd3\xff\x00\xe3)\xf5\xab\xfe\xe5`\x7f\xdb\x97\x7f\xef*_\xe2S\xff\x00\x15Y_\xf8B\xcf\xfc\xfd\x8a\xbd\xad%<\xbf\xf8\xbc\xfa\xaf\xd4>\xab\xf4[\xb03\xec\xa6\xdbm\xc9u\xedu\x05\xcen\xd7WM@\x13mt\xbb~\xea]\xf9\xab\xa8I$\x94\xa4\x92I%?\xff\xd5\xf5T\x92I%)$\x92IJ\\\x7f_\xff\x00\x15\xfd\x03\xafuk\xfa\xb6fF]y\x19;7\xb6\xa7\xd6\x1861\x947kl\xa2\xd7\xfd\n\xbf}v\t$\xa7\xcf\xff\x00\xf1\x94\xfa\xab\xff\x00r\xb3\xff\x00\xed\xca\x7f\xf7\x95/\xfce>\xaa\xff\x00\xdc\xac\xff\x00\xfbr\x9f\xfd\xe5^\x80\x92J|\xff\x00\xff\x00\x19O\xaa\xbf\xf7+?\xfe\xdc\xa7\xff\x00yR\xff\x00\xc6S\xea\xaf\xfd\xca\xcf\xff\x00\xb7)\xff\x00\xdeU\xe8\t$\xa7\x97\xfa\xaf\xfe/:/\xd5~\xa1f~\x05\xd96\xdbm&\x876\xf7V\xe6\xeds\xab\xb4\x90*\xa6\x97o\xddK\x7f9u\t$\x92\x94\x92I$\xa5$\x92I)\xff\xd6\xf5T\x92I%)$\x92IJI$\x92R\x92I$\x94\xa4\x92I%)$\x92IJI$\x92R\x92I$\x94\xff\x00\xff\xd9\x008BIM\x04\x06\x00\x00\x00\x00\x00\x07\x00\x03\x00\x00\x00\x01\x01\x00\xff\xfe\x00\'File written by Adobe Photoshop\xa8 4.0\x00\xff\xee\x00\x0eAdobe\x00d\x00\x00\x00\x00\x01\xff\xdb\x00\x84\x00\n\x07\x07\x07\x08\x07\n\x08\x08\n\x0f\n\x08\n\x0f\x12\r\n\n\r\x12\x14\x10\x10\x12\x10\x10\x14\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x01\x0b\x0c\x0c\x15\x13\x15"\x18\x18"\x14\x0e\x0e\x0e\x14\x14\x0e\x0e\x0e\x0e\x14\x11\x0c\x0c\x0c\x0c\x0c\x11\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\xff\xc0\x00\x11\x08\x00\t\x00\t\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xdd\x00\x04\x00\x02\xff\xc4\x01\xa2\x00\x00\x00\x07\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x04\x05\x03\x02\x06\x01\x00\x07\x08\t\n\x0b\x01\x00\x02\x02\x03\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x10\x00\x02\x01\x03\x03\x02\x04\x02\x06\x07\x03\x04\x02\x06\x02s\x01\x02\x03\x11\x04\x00\x05!\x121AQ\x06\x13a"q\x81\x142\x91\xa1\x07\x15\xb1B#\xc1R\xd1\xe13\x16b\xf0$r\x82\xf1%C4S\x92\xa2\xb2cs\xc25D\'\x93\xa3\xb36\x17Tdt\xc3\xd2\xe2\x08&\x83\t\n\x18\x19\x84\x94EF\xa4\xb4V\xd3U(\x1a\xf2\xe3\xf3\xc4\xd4\xe4\xf4eu\x85\x95\xa5\xb5\xc5\xd5\xe5\xf5fv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf67GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf78HXhx\x88\x98\xa8\xb8\xc8\xd8\xe8\xf8)9IYiy\x89\x99\xa9\xb9\xc9\xd9\xe9\xf9*:JZjz\x8a\x9a\xaa\xba\xca\xda\xea\xfa\x11\x00\x02\x02\x01\x02\x03\x05\x05\x04\x05\x06\x04\x08\x03\x03m\x01\x00\x02\x11\x03\x04!\x121A\x05Q\x13a"\x06q\x81\x912\xa1\xb1\xf0\x14\xc1\xd1\xe1#B\x15Rbr\xf13$4C\x82\x16\x92S%\xa2c\xb2\xc2\x07s\xd25\xe2D\x83\x17T\x93\x08\t\n\x18\x19&6E\x1a\'dtU7\xf2\xa3\xb3\xc3()\xd3\xe3\xf3\x84\x94\xa4\xb4\xc4\xd4\xe4\xf4eu\x85\x95\xa5\xb5\xc5\xd5\xe5\xf5FVfv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf6GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf78HXhx\x88\x98\xa8\xb8\xc8\xd8\xe8\xf89IYiy\x89\x99\xa9\xb9\xc9\xd9\xe9\xf9*:JZjz\x8a\x9a\xaa\xba\xca\xda\xea\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x91\xea\xfa\xbf\xe6D_\x99\x16\x96\x16\x16\x8c\xdeWf\x84;\x88U\xa1hY\x7f\xd3\'\x9e\xf3\xedCq\x0bz\xfe\x94^\xbc?\xdc\xdb\xff\x00\xa3\xcd\xeb\x7f\xa4\xaa\xf4<U\xff\xd0\xec\xd8\xab\xb1W\xff\xd9'
+    return b"\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x02\x01\x00H\x00H\x00\x00\xff\xed\x08\xaePhotoshop 3.0\x008BIM\x03\xe9\x00\x00\x00\x00\x00x\x00\x03\x00\x00\x00H\x00H\x00\x00\x00\x00\x02\xd8\x02(\xff\xe1\xff\xe2\x02\xf9\x02F\x03G\x05(\x03\xfc\x00\x02\x00\x00\x00H\x00H\x00\x00\x00\x00\x02\xd8\x02(\x00\x01\x00\x00\x00d\x00\x00\x00\x01\x00\x03\x03\x03\x00\x00\x00\x01'\x0f\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x08\x00\x19\x01\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008BIM\x03\xed\x00\x00\x00\x00\x00\x10\x00H\x00\x00\x00\x01\x00\x01\x00H\x00\x00\x00\x01\x00\x018BIM\x03\xf3\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x008BIM\x04\n\x00\x00\x00\x00\x00\x01\x00\x008BIM'\x10\x00\x00\x00\x00\x00\n\x00\x01\x00\x00\x00\x00\x00\x00\x00\x028BIM\x03\xf5\x00\x00\x00\x00\x00H\x00/ff\x00\x01\x00lff\x00\x06\x00\x00\x00\x00\x00\x01\x00/ff\x00\x01\x00\xa1\x99\x9a\x00\x06\x00\x00\x00\x00\x00\x01\x002\x00\x00\x00\x01\x00Z\x00\x00\x00\x06\x00\x00\x00\x00\x00\x01\x005\x00\x00\x00\x01\x00-\x00\x00\x00\x06\x00\x00\x00\x00\x00\x018BIM\x03\xf8\x00\x00\x00\x00\x00p\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x008BIM\x04\x00\x00\x00\x00\x00\x00\x02\x00\x018BIM\x04\x02\x00\x00\x00\x00\x00\x04\x00\x00\x00\x008BIM\x04\x08\x00\x00\x00\x00\x00\x10\x00\x00\x00\x01\x00\x00\x02@\x00\x00\x02@\x00\x00\x00\x008BIM\x04\t\x00\x00\x00\x00\x06\x9b\x00\x00\x00\x01\x00\x00\x00\x80\x00\x00\x00\x80\x00\x00\x01\x80\x00\x00\xc0\x00\x00\x00\x06\x7f\x00\x18\x00\x01\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x02\x01\x00H\x00H\x00\x00\xff\xfe\x00'File written by Adobe Photoshop\xa8 4.0\x00\xff\xee\x00\x0eAdobe\x00d\x80\x00\x00\x00\x01\xff\xdb\x00\x84\x00\x0c\x08\x08\x08\t\x08\x0c\t\t\x0c\x11\x0b\n\x0b\x11\x15\x0f\x0c\x0c\x0f\x15\x18\x13\x13\x15\x13\x13\x18\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x01\r\x0b\x0b\r\x0e\r\x10\x0e\x0e\x10\x14\x0e\x0e\x0e\x14\x14\x0e\x0e\x0e\x0e\x14\x11\x0c\x0c\x0c\x0c\x0c\x11\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\xff\xc0\x00\x11\x08\x00\x80\x00\x80\x03\x01\"\x00\x02\x11\x01\x03\x11\x01\xff\xdd\x00\x04\x00\x08\xff\xc4\x01?\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x01\x02\x04\x05\x06\x07\x08\t\n\x0b\x01\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x10\x00\x01\x04\x01\x03\x02\x04\x02\x05\x07\x06\x08\x05\x03\x0c3\x01\x00\x02\x11\x03\x04!\x121\x05AQa\x13\"q\x812\x06\x14\x91\xa1\xb1B#$\x15R\xc1b34r\x82\xd1C\x07%\x92S\xf0\xe1\xf1cs5\x16\xa2\xb2\x83&D\x93TdE\xc2\xa3t6\x17\xd2U\xe2e\xf2\xb3\x84\xc3\xd3u\xe3\xf3F'\x94\xa4\x85\xb4\x95\xc4\xd4\xe4\xf4\xa5\xb5\xc5\xd5\xe5\xf5Vfv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf67GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf7\x11\x00\x02\x02\x01\x02\x04\x04\x03\x04\x05\x06\x07\x07\x06\x055\x01\x00\x02\x11\x03!1\x12\x04AQaq\"\x13\x052\x81\x91\x14\xa1\xb1B#\xc1R\xd1\xf03$b\xe1r\x82\x92CS\x15cs4\xf1%\x06\x16\xa2\xb2\x83\x07&5\xc2\xd2D\x93T\xa3\x17dEU6te\xe2\xf2\xb3\x84\xc3\xd3u\xe3\xf3F\x94\xa4\x85\xb4\x95\xc4\xd4\xe4\xf4\xa5\xb5\xc5\xd5\xe5\xf5Vfv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf6'7GWgw\x87\x97\xa7\xb7\xc7\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\xf5T\x92I%)$\x92IJI$\x92R\x92I$\x94\xa4\x92I%)$\x92IJI$\x92R\x92I$\x94\xff\x00\xff\xd0\xf5T\x92I%)$\x92IJI%\xe7\xff\x00Z\x7f\xc6\xbf\xfc\xde\xeb\xb9]\x1f\xf6_\xda~\xcd\xe9\xfe\x9b\xed\x1e\x9e\xefR\xba\xef\xfeo\xec\xf6\xed\xdb\xea\xec\xfeq%>\x80\x92\xf2\xaf\xfc}?\xf3I\xff\x00\xb3_\xfb\xe8\x97\xfe>\x9f\xf9\xa4\xff\x00\xd9\xaf\xfd\xf4IO\xaa\xa4\xbc\xab\xff\x00\x1fO\xfc\xd2\x7f\xec\xd7\xfe\xfa%\xff\x00\x8f\xa7\xfei?\xf6k\xff\x00}\x12S\xea\xa9.+\xeaW\xf8\xc8\xff\x00\x9d}V\xde\x9d\xfb;\xec~\x96;\xb2=O[\xd5\x9d\xaf\xaa\xad\x9b=\n\x7f\xd3}-\xeb\xb5IJI$\x92R\x92I$\x94\xff\x00\xff\xd1\xf5T\x92I%)$\x97\x9f\xff\x00\x8d\x7f\xad=w\xea\xf7\xec\xbf\xd8\xf9_f\xfbO\xda=o\xd1\xd7f\xefO\xec\xfe\x9f\xf3\xf5\xdb\xb7o\xabg\xd0IO\xa0/\x9f\xff\x00\xc6\x97\xfe.\xfa\x9f\xfdc\xff\x00m\xf1\xd2\xff\x00\xc7K\xeb\xdf\xfeY\xff\x00\xe0\x18\xff\x00\xfb\xce\xb9\xfe\xa9\xd53\xfa\xbe}\xbdG\xa8\xdb\xeb\xe5\xdf\xb7\xd4\xb3kY;\x1a\xda\x99\xec\xa9\xac\xaf\xf9\xb63\xf3\x12SU$\x92IJI$\x92S\xdf\xff\x00\x89O\xfcUe\x7f\xe1\x0b?\xf3\xf6*\xf6\xb5\xf3/D\xeb\xfd[\xa0\xe5?3\xa4\xdf\xf6l\x8b+59\xfb\x18\xf9a-\xb1\xcd\xdb{-g\xd3\xa9\x8bk\xff\x00\x1d/\xaf\x7f\xf9g\xff\x00\x80c\xff\x00\xef:J~\x80Iq\xff\x00\xe2\xbf\xaf\xf5n\xbd\xd023:\xb5\xff\x00i\xc8\xaf-\xf55\xfb\x18\xc8`\xae\x8b\x1a\xdd\xb42\xa6};^\xbb\x04\x94\xa4\x92I%?\xff\xd2\xf5T\x92I%)yW\xf8\xf4\xff\x00\xbcO\xfd\n\xff\x00\xddE\xea\xab\xca\xbf\xc7\xa7\xfd\xe2\x7f\xe8W\xfe\xea$\xa7\xca\x92I$\x94\xa4\x92I%)$\x92IJI$\x92S\xed_\xe2S\xff\x00\x12\xb9_\xf8~\xcf\xfc\xf3\x8a\xbd\x01y\xff\x00\xf8\x94\xff\x00\xc4\xaeW\xfe\x1f\xb3\xff\x00<\xe2\xaf@IJI$\x92S\xff\xd3\xf5T\x92I%)yW\xf8\xf4\xff\x00\xbcO\xfd\n\xff\x00\xddE\xea\xab\xca\xbf\xc7\xa7\xfd\xe2\x7f\xe8W\xfe\xea$\xa7\xca\x92I$\x94\xa4\x92I%)$\x92IJI$\x92S\xed_\xe2S\xff\x00\x12\xb9_\xf8~\xcf\xfc\xf3\x8a\xbd\x01y\xff\x00\xf8\x94\xff\x00\xc4\xaeW\xfe\x1f\xb3\xff\x00<\xe2\xaf@IJI$\x92S\xff\xd4\xf5T\x92I%)q_\xe3#\xeaWU\xfa\xd7\xfb;\xf6u\xb8\xf5}\x8f\xd6\xf5>\xd0\xe7\xb6}_Cf\xcfJ\xab\xbf\xd0\xbfr\xedRIO\x8a\x7f\xe3)\xf5\xab\xfe\xe5`\x7f\xdb\x97\x7f\xef*\xe4:\xff\x00D\xca\xe8=Z\xfe\x93\x98\xfa\xec\xc8\xc6\xd9\xbd\xd5\x12Xw\xb1\x97\xb7k\xacmO\xfa\x16\xfe\xe2\xfai|\xff\x00\xfe4\xbf\xf1w\xd4\xff\x00\xeb\x1f\xfbo\x8e\x92\x9eU$\x92IJI$\x92S\xb1\xf5_\xea\xbfP\xfa\xd1\xd4,\xc0\xc0\xb2\x9a\xad\xaa\x93{\x9dys[\xb5\xae\xae\xa2\x01\xaa\xbb\x9d\xbfu\xcd\xfc\xd5\xd3\xff\x00\xe3)\xf5\xab\xfe\xe5`\x7f\xdb\x97\x7f\xef*_\xe2S\xff\x00\x15Y_\xf8B\xcf\xfc\xfd\x8a\xbd\xad%<\xbf\xf8\xbc\xfa\xaf\xd4>\xab\xf4[\xb03\xec\xa6\xdbm\xc9u\xedu\x05\xcen\xd7WM@\x13mt\xbb~\xea]\xf9\xab\xa8I$\x94\xa4\x92I%?\xff\xd5\xf5T\x92I%)$\x92IJ\\\x7f_\xff\x00\x15\xfd\x03\xafuk\xfa\xb6fF]y\x19;7\xb6\xa7\xd6\x1861\x947kl\xa2\xd7\xfd\n\xbf}v\t$\xa7\xcf\xff\x00\xf1\x94\xfa\xab\xff\x00r\xb3\xff\x00\xed\xca\x7f\xf7\x95/\xfce>\xaa\xff\x00\xdc\xac\xff\x00\xfbr\x9f\xfd\xe5^\x80\x92J|\xff\x00\xff\x00\x19O\xaa\xbf\xf7+?\xfe\xdc\xa7\xff\x00yR\xff\x00\xc6S\xea\xaf\xfd\xca\xcf\xff\x00\xb7)\xff\x00\xdeU\xe8\t$\xa7\x97\xfa\xaf\xfe/:/\xd5~\xa1f~\x05\xd96\xdbm&\x876\xf7V\xe6\xeds\xab\xb4\x90*\xa6\x97o\xddK\x7f9u\t$\x92\x94\x92I$\xa5$\x92I)\xff\xd6\xf5T\x92I%)$\x92IJI$\x92R\x92I$\x94\xa4\x92I%)$\x92IJI$\x92R\x92I$\x94\xff\x00\xff\xd9\x008BIM\x04\x06\x00\x00\x00\x00\x00\x07\x00\x03\x00\x00\x00\x01\x01\x00\xff\xfe\x00'File written by Adobe Photoshop\xa8 4.0\x00\xff\xee\x00\x0eAdobe\x00d\x00\x00\x00\x00\x01\xff\xdb\x00\x84\x00\n\x07\x07\x07\x08\x07\n\x08\x08\n\x0f\n\x08\n\x0f\x12\r\n\n\r\x12\x14\x10\x10\x12\x10\x10\x14\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x01\x0b\x0c\x0c\x15\x13\x15\"\x18\x18\"\x14\x0e\x0e\x0e\x14\x14\x0e\x0e\x0e\x0e\x14\x11\x0c\x0c\x0c\x0c\x0c\x11\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\xff\xc0\x00\x11\x08\x00\t\x00\t\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xdd\x00\x04\x00\x02\xff\xc4\x01\xa2\x00\x00\x00\x07\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x04\x05\x03\x02\x06\x01\x00\x07\x08\t\n\x0b\x01\x00\x02\x02\x03\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x10\x00\x02\x01\x03\x03\x02\x04\x02\x06\x07\x03\x04\x02\x06\x02s\x01\x02\x03\x11\x04\x00\x05!\x121AQ\x06\x13a\"q\x81\x142\x91\xa1\x07\x15\xb1B#\xc1R\xd1\xe13\x16b\xf0$r\x82\xf1%C4S\x92\xa2\xb2cs\xc25D'\x93\xa3\xb36\x17Tdt\xc3\xd2\xe2\x08&\x83\t\n\x18\x19\x84\x94EF\xa4\xb4V\xd3U(\x1a\xf2\xe3\xf3\xc4\xd4\xe4\xf4eu\x85\x95\xa5\xb5\xc5\xd5\xe5\xf5fv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf67GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf78HXhx\x88\x98\xa8\xb8\xc8\xd8\xe8\xf8)9IYiy\x89\x99\xa9\xb9\xc9\xd9\xe9\xf9*:JZjz\x8a\x9a\xaa\xba\xca\xda\xea\xfa\x11\x00\x02\x02\x01\x02\x03\x05\x05\x04\x05\x06\x04\x08\x03\x03m\x01\x00\x02\x11\x03\x04!\x121A\x05Q\x13a\"\x06q\x81\x912\xa1\xb1\xf0\x14\xc1\xd1\xe1#B\x15Rbr\xf13$4C\x82\x16\x92S%\xa2c\xb2\xc2\x07s\xd25\xe2D\x83\x17T\x93\x08\t\n\x18\x19&6E\x1a'dtU7\xf2\xa3\xb3\xc3()\xd3\xe3\xf3\x84\x94\xa4\xb4\xc4\xd4\xe4\xf4eu\x85\x95\xa5\xb5\xc5\xd5\xe5\xf5FVfv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf6GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf78HXhx\x88\x98\xa8\xb8\xc8\xd8\xe8\xf89IYiy\x89\x99\xa9\xb9\xc9\xd9\xe9\xf9*:JZjz\x8a\x9a\xaa\xba\xca\xda\xea\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x91\xea\xfa\xbf\xe6D_\x99\x16\x96\x16\x16\x8c\xdeWf\x84;\x88U\xa1hY\x7f\xd3'\x9e\xf3\xedCq\x0bz\xfe\x94^\xbc?\xdc\xdb\xff\x00\xa3\xcd\xeb\x7f\xa4\xaa\xf4<U\xff\xd0\xec\xd8\xab\xb1W\xff\xd9"
diff --git a/api/tests/music/test_activity.py b/api/tests/music/test_activity.py
index f604874c14..0d9879300c 100644
--- a/api/tests/music/test_activity.py
+++ b/api/tests/music/test_activity.py
@@ -2,16 +2,13 @@ from funkwhale_api.users.serializers import UserActivitySerializer
 from funkwhale_api.favorites import serializers
 
 
-
 def test_get_track_activity_url_mbid(factories):
-    track = factories['music.Track']()
-    expected = 'https://musicbrainz.org/recording/{}'.format(
-        track.mbid)
+    track = factories["music.Track"]()
+    expected = "https://musicbrainz.org/recording/{}".format(track.mbid)
     assert track.get_activity_url() == expected
 
 
 def test_get_track_activity_url_no_mbid(settings, factories):
-    track = factories['music.Track'](mbid=None)
-    expected = settings.FUNKWHALE_URL + '/tracks/{}'.format(
-        track.pk)
+    track = factories["music.Track"](mbid=None)
+    expected = settings.FUNKWHALE_URL + "/tracks/{}".format(track.pk)
     assert track.get_activity_url() == expected
diff --git a/api/tests/music/test_api.py b/api/tests/music/test_api.py
index 7aa20e6262..d7d37bd4b1 100644
--- a/api/tests/music/test_api.py
+++ b/api/tests/music/test_api.py
@@ -13,234 +13,235 @@ DATA_DIR = os.path.dirname(os.path.abspath(__file__))
 
 
 def test_can_submit_youtube_url_for_track_import(
-        settings, artists, albums, tracks, mocker, superuser_client):
-    mocker.patch('funkwhale_api.music.tasks.import_job_run.delay')
+    settings, artists, albums, tracks, mocker, superuser_client
+):
+    mocker.patch("funkwhale_api.music.tasks.import_job_run.delay")
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.artists.get',
-        return_value=artists['get']['adhesive_wombat'])
+        "funkwhale_api.musicbrainz.api.artists.get",
+        return_value=artists["get"]["adhesive_wombat"],
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.releases.get',
-        return_value=albums['get']['marsupial'])
+        "funkwhale_api.musicbrainz.api.releases.get",
+        return_value=albums["get"]["marsupial"],
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.recordings.get',
-        return_value=tracks['get']['8bitadventures'])
+        "funkwhale_api.musicbrainz.api.recordings.get",
+        return_value=tracks["get"]["8bitadventures"],
+    )
     mocker.patch(
-        'funkwhale_api.music.models.TrackFile.download_file',
-        return_value=None)
-    mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
-    video_id = 'tPEE9ZwTmy0'
-    url = reverse('api:v1:submit-single')
-    video_url = 'https://www.youtube.com/watch?v={0}'.format(video_id)
-    response = superuser_client.post(
-        url,
-        {'import_url': video_url,
-         'mbid': mbid})
+        "funkwhale_api.music.models.TrackFile.download_file", return_value=None
+    )
+    mbid = "9968a9d6-8d92-4051-8f76-674e157b6eed"
+    video_id = "tPEE9ZwTmy0"
+    url = reverse("api:v1:submit-single")
+    video_url = "https://www.youtube.com/watch?v={0}".format(video_id)
+    response = superuser_client.post(url, {"import_url": video_url, "mbid": mbid})
 
     assert response.status_code == 201
-    batch = superuser_client.user.imports.latest('id')
-    job = batch.jobs.latest('id')
-    assert job.status == 'pending'
+    batch = superuser_client.user.imports.latest("id")
+    job = batch.jobs.latest("id")
+    assert job.status == "pending"
     assert str(job.mbid) == mbid
     assert job.source == video_url
 
 
 def test_import_creates_an_import_with_correct_data(mocker, superuser_client):
-    mocker.patch('funkwhale_api.music.tasks.import_job_run')
-    mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
-    video_id = 'tPEE9ZwTmy0'
-    url = reverse('api:v1:submit-single')
+    mocker.patch("funkwhale_api.music.tasks.import_job_run")
+    mbid = "9968a9d6-8d92-4051-8f76-674e157b6eed"
+    video_id = "tPEE9ZwTmy0"
+    url = reverse("api:v1:submit-single")
     response = superuser_client.post(
         url,
-        {'import_url': 'https://www.youtube.com/watch?v={0}'.format(video_id),
-         'mbid': mbid})
+        {
+            "import_url": "https://www.youtube.com/watch?v={0}".format(video_id),
+            "mbid": mbid,
+        },
+    )
 
-    batch = models.ImportBatch.objects.latest('id')
+    batch = models.ImportBatch.objects.latest("id")
     assert batch.jobs.count() == 1
     assert batch.submitted_by == superuser_client.user
-    assert batch.status == 'pending'
+    assert batch.status == "pending"
     job = batch.jobs.first()
     assert str(job.mbid) == mbid
-    assert job.status == 'pending'
-    assert job.source == 'https://www.youtube.com/watch?v={0}'.format(video_id)
+    assert job.status == "pending"
+    assert job.source == "https://www.youtube.com/watch?v={0}".format(video_id)
 
 
-def test_can_import_whole_album(
-        artists, albums, mocker, superuser_client):
-    mocker.patch('funkwhale_api.music.tasks.import_job_run')
+def test_can_import_whole_album(artists, albums, mocker, superuser_client):
+    mocker.patch("funkwhale_api.music.tasks.import_job_run")
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.artists.get',
-        return_value=artists['get']['soad'])
+        "funkwhale_api.musicbrainz.api.artists.get", return_value=artists["get"]["soad"]
+    )
+    mocker.patch("funkwhale_api.musicbrainz.api.images.get_front", return_value=b"")
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.images.get_front',
-        return_value=b'')
-    mocker.patch(
-        'funkwhale_api.musicbrainz.api.releases.get',
-        return_value=albums['get_with_includes']['hypnotize'])
+        "funkwhale_api.musicbrainz.api.releases.get",
+        return_value=albums["get_with_includes"]["hypnotize"],
+    )
     payload = {
-        'releaseId': '47ae093f-1607-49a3-be11-a15d335ccc94',
-        'tracks': [
+        "releaseId": "47ae093f-1607-49a3-be11-a15d335ccc94",
+        "tracks": [
             {
-            'mbid': '1968a9d6-8d92-4051-8f76-674e157b6eed',
-            'source': 'https://www.youtube.com/watch?v=1111111111',
+                "mbid": "1968a9d6-8d92-4051-8f76-674e157b6eed",
+                "source": "https://www.youtube.com/watch?v=1111111111",
             },
             {
-            'mbid': '2968a9d6-8d92-4051-8f76-674e157b6eed',
-            'source': 'https://www.youtube.com/watch?v=2222222222',
+                "mbid": "2968a9d6-8d92-4051-8f76-674e157b6eed",
+                "source": "https://www.youtube.com/watch?v=2222222222",
             },
             {
-            'mbid': '3968a9d6-8d92-4051-8f76-674e157b6eed',
-            'source': 'https://www.youtube.com/watch?v=3333333333',
+                "mbid": "3968a9d6-8d92-4051-8f76-674e157b6eed",
+                "source": "https://www.youtube.com/watch?v=3333333333",
             },
-        ]
+        ],
     }
-    url = reverse('api:v1:submit-album')
+    url = reverse("api:v1:submit-album")
     response = superuser_client.post(
-        url, json.dumps(payload), content_type="application/json")
+        url, json.dumps(payload), content_type="application/json"
+    )
 
-    batch = models.ImportBatch.objects.latest('id')
+    batch = models.ImportBatch.objects.latest("id")
     assert batch.jobs.count() == 3
     assert batch.submitted_by == superuser_client.user
-    assert batch.status == 'pending'
+    assert batch.status == "pending"
 
-    album = models.Album.objects.latest('id')
-    assert str(album.mbid) == '47ae093f-1607-49a3-be11-a15d335ccc94'
-    medium_data = albums['get_with_includes']['hypnotize']['release']['medium-list'][0]
-    assert int(medium_data['track-count']) == album.tracks.all().count()
+    album = models.Album.objects.latest("id")
+    assert str(album.mbid) == "47ae093f-1607-49a3-be11-a15d335ccc94"
+    medium_data = albums["get_with_includes"]["hypnotize"]["release"]["medium-list"][0]
+    assert int(medium_data["track-count"]) == album.tracks.all().count()
 
-    for track in medium_data['track-list']:
-        instance = models.Track.objects.get(mbid=track['recording']['id'])
-        assert instance.title == track['recording']['title']
-        assert instance.position == int(track['position'])
-        assert instance.title == track['recording']['title']
+    for track in medium_data["track-list"]:
+        instance = models.Track.objects.get(mbid=track["recording"]["id"])
+        assert instance.title == track["recording"]["title"]
+        assert instance.position == int(track["position"])
+        assert instance.title == track["recording"]["title"]
 
-    for row in payload['tracks']:
-        job = models.ImportJob.objects.get(mbid=row['mbid'])
-        assert str(job.mbid) == row['mbid']
-        assert job.status == 'pending'
-        assert job.source == row['source']
+    for row in payload["tracks"]:
+        job = models.ImportJob.objects.get(mbid=row["mbid"])
+        assert str(job.mbid) == row["mbid"]
+        assert job.status == "pending"
+        assert job.source == row["source"]
 
 
-def test_can_import_whole_artist(
-        artists, albums, mocker, superuser_client):
-    mocker.patch('funkwhale_api.music.tasks.import_job_run')
-    mocker.patch(
-        'funkwhale_api.musicbrainz.api.artists.get',
-        return_value=artists['get']['soad'])
+def test_can_import_whole_artist(artists, albums, mocker, superuser_client):
+    mocker.patch("funkwhale_api.music.tasks.import_job_run")
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.images.get_front',
-        return_value=b'')
+        "funkwhale_api.musicbrainz.api.artists.get", return_value=artists["get"]["soad"]
+    )
+    mocker.patch("funkwhale_api.musicbrainz.api.images.get_front", return_value=b"")
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.releases.get',
-        return_value=albums['get_with_includes']['hypnotize'])
+        "funkwhale_api.musicbrainz.api.releases.get",
+        return_value=albums["get_with_includes"]["hypnotize"],
+    )
     payload = {
-        'artistId': 'mbid',
-        'albums': [
+        "artistId": "mbid",
+        "albums": [
             {
-                'releaseId': '47ae093f-1607-49a3-be11-a15d335ccc94',
-                'tracks': [
+                "releaseId": "47ae093f-1607-49a3-be11-a15d335ccc94",
+                "tracks": [
                     {
-                    'mbid': '1968a9d6-8d92-4051-8f76-674e157b6eed',
-                    'source': 'https://www.youtube.com/watch?v=1111111111',
+                        "mbid": "1968a9d6-8d92-4051-8f76-674e157b6eed",
+                        "source": "https://www.youtube.com/watch?v=1111111111",
                     },
                     {
-                    'mbid': '2968a9d6-8d92-4051-8f76-674e157b6eed',
-                    'source': 'https://www.youtube.com/watch?v=2222222222',
+                        "mbid": "2968a9d6-8d92-4051-8f76-674e157b6eed",
+                        "source": "https://www.youtube.com/watch?v=2222222222",
                     },
                     {
-                    'mbid': '3968a9d6-8d92-4051-8f76-674e157b6eed',
-                    'source': 'https://www.youtube.com/watch?v=3333333333',
+                        "mbid": "3968a9d6-8d92-4051-8f76-674e157b6eed",
+                        "source": "https://www.youtube.com/watch?v=3333333333",
                     },
-                ]
+                ],
             }
-        ]
+        ],
     }
-    url = reverse('api:v1:submit-artist')
+    url = reverse("api:v1:submit-artist")
     response = superuser_client.post(
-        url, json.dumps(payload), content_type="application/json")
+        url, json.dumps(payload), content_type="application/json"
+    )
 
-    batch = models.ImportBatch.objects.latest('id')
+    batch = models.ImportBatch.objects.latest("id")
     assert batch.jobs.count() == 3
     assert batch.submitted_by == superuser_client.user
-    assert batch.status == 'pending'
+    assert batch.status == "pending"
 
-    album = models.Album.objects.latest('id')
-    assert str(album.mbid) == '47ae093f-1607-49a3-be11-a15d335ccc94'
-    medium_data = albums['get_with_includes']['hypnotize']['release']['medium-list'][0]
-    assert int(medium_data['track-count']) == album.tracks.all().count()
+    album = models.Album.objects.latest("id")
+    assert str(album.mbid) == "47ae093f-1607-49a3-be11-a15d335ccc94"
+    medium_data = albums["get_with_includes"]["hypnotize"]["release"]["medium-list"][0]
+    assert int(medium_data["track-count"]) == album.tracks.all().count()
 
-    for track in medium_data['track-list']:
-        instance = models.Track.objects.get(mbid=track['recording']['id'])
-        assert instance.title == track['recording']['title']
-        assert instance.position == int(track['position'])
-        assert instance.title == track['recording']['title']
+    for track in medium_data["track-list"]:
+        instance = models.Track.objects.get(mbid=track["recording"]["id"])
+        assert instance.title == track["recording"]["title"]
+        assert instance.position == int(track["position"])
+        assert instance.title == track["recording"]["title"]
 
-    for row in payload['albums'][0]['tracks']:
-        job = models.ImportJob.objects.get(mbid=row['mbid'])
-        assert str(job.mbid) == row['mbid']
-        assert job.status == 'pending'
-        assert job.source == row['source']
+    for row in payload["albums"][0]["tracks"]:
+        job = models.ImportJob.objects.get(mbid=row["mbid"])
+        assert str(job.mbid) == row["mbid"]
+        assert job.status == "pending"
+        assert job.source == row["source"]
 
 
 def test_user_can_create_an_empty_batch(superuser_api_client, factories):
-    url = reverse('api:v1:import-batches-list')
+    url = reverse("api:v1:import-batches-list")
     response = superuser_api_client.post(url)
 
     assert response.status_code == 201
 
-    batch = superuser_api_client.user.imports.latest('id')
+    batch = superuser_api_client.user.imports.latest("id")
 
     assert batch.submitted_by == superuser_api_client.user
-    assert batch.source == 'api'
+    assert batch.source == "api"
 
 
-def test_user_can_create_import_job_with_file(
-        superuser_api_client, factories, mocker):
-    path = os.path.join(DATA_DIR, 'test.ogg')
-    m = mocker.patch('funkwhale_api.common.utils.on_commit')
-    batch = factories['music.ImportBatch'](
-        submitted_by=superuser_api_client.user)
-    url = reverse('api:v1:import-jobs-list')
-    with open(path, 'rb') as f:
+def test_user_can_create_import_job_with_file(superuser_api_client, factories, mocker):
+    path = os.path.join(DATA_DIR, "test.ogg")
+    m = mocker.patch("funkwhale_api.common.utils.on_commit")
+    batch = factories["music.ImportBatch"](submitted_by=superuser_api_client.user)
+    url = reverse("api:v1:import-jobs-list")
+    with open(path, "rb") as f:
         content = f.read()
         f.seek(0)
-        response = superuser_api_client.post(url, {
-            'batch': batch.pk,
-            'audio_file': f,
-            'source': 'file://'
-        })
+        response = superuser_api_client.post(
+            url, {"batch": batch.pk, "audio_file": f, "source": "file://"}
+        )
 
     assert response.status_code == 201
 
-    job = batch.jobs.latest('id')
+    job = batch.jobs.latest("id")
 
-    assert job.status == 'pending'
-    assert job.source.startswith('file://')
-    assert 'test.ogg' in job.source
+    assert job.status == "pending"
+    assert job.source.startswith("file://")
+    assert "test.ogg" in job.source
     assert job.audio_file.read() == content
 
-    m.assert_called_once_with(
-        tasks.import_job_run.delay,
-        import_job_id=job.pk)
+    m.assert_called_once_with(tasks.import_job_run.delay, import_job_id=job.pk)
 
 
-@pytest.mark.parametrize('route,method', [
-    ('api:v1:tags-list', 'get'),
-    ('api:v1:tracks-list', 'get'),
-    ('api:v1:artists-list', 'get'),
-    ('api:v1:albums-list', 'get'),
-])
+@pytest.mark.parametrize(
+    "route,method",
+    [
+        ("api:v1:tags-list", "get"),
+        ("api:v1:tracks-list", "get"),
+        ("api:v1:artists-list", "get"),
+        ("api:v1:albums-list", "get"),
+    ],
+)
 def test_can_restrict_api_views_to_authenticated_users(
-        db, route, method, preferences, client):
+    db, route, method, preferences, client
+):
     url = reverse(route)
-    preferences['common__api_authentication_required'] = True
+    preferences["common__api_authentication_required"] = True
     response = getattr(client, method)(url)
     assert response.status_code == 401
 
 
 def test_track_file_url_is_restricted_to_authenticated_users(
-        api_client, factories, preferences):
-    preferences['common__api_authentication_required'] = True
-    f = factories['music.TrackFile']()
+    api_client, factories, preferences
+):
+    preferences["common__api_authentication_required"] = True
+    f = factories["music.TrackFile"]()
     assert f.audio_file is not None
     url = f.path
     response = api_client.get(url)
@@ -248,12 +249,13 @@ def test_track_file_url_is_restricted_to_authenticated_users(
 
 
 def test_track_file_url_is_accessible_to_authenticated_users(
-        logged_in_api_client, factories, preferences):
-    preferences['common__api_authentication_required'] = True
-    f = factories['music.TrackFile']()
+    logged_in_api_client, factories, preferences
+):
+    preferences["common__api_authentication_required"] = True
+    f = factories["music.TrackFile"]()
     assert f.audio_file is not None
     url = f.path
     response = logged_in_api_client.get(url)
 
     assert response.status_code == 200
-    assert response['X-Accel-Redirect'] == '/_protected{}'.format(f.audio_file.url)
+    assert response["X-Accel-Redirect"] == "/_protected{}".format(f.audio_file.url)
diff --git a/api/tests/music/test_commands.py b/api/tests/music/test_commands.py
index 6f03f6b8ad..9f8bad6c29 100644
--- a/api/tests/music/test_commands.py
+++ b/api/tests/music/test_commands.py
@@ -6,13 +6,14 @@ DATA_DIR = os.path.dirname(os.path.abspath(__file__))
 
 
 def test_fix_track_files_bitrate_length(factories, mocker):
-    tf1 = factories['music.TrackFile'](bitrate=1, duration=2)
-    tf2 = factories['music.TrackFile'](bitrate=None, duration=None)
+    tf1 = factories["music.TrackFile"](bitrate=1, duration=2)
+    tf2 = factories["music.TrackFile"](bitrate=None, duration=None)
     c = fix_track_files.Command()
 
     mocker.patch(
-        'funkwhale_api.music.utils.get_audio_file_data',
-        return_value={'bitrate': 42, 'length': 43})
+        "funkwhale_api.music.utils.get_audio_file_data",
+        return_value={"bitrate": 42, "length": 43},
+    )
 
     c.fix_file_data(dry_run=False)
 
@@ -29,13 +30,11 @@ def test_fix_track_files_bitrate_length(factories, mocker):
 
 
 def test_fix_track_files_size(factories, mocker):
-    tf1 = factories['music.TrackFile'](size=1)
-    tf2 = factories['music.TrackFile'](size=None)
+    tf1 = factories["music.TrackFile"](size=1)
+    tf2 = factories["music.TrackFile"](size=None)
     c = fix_track_files.Command()
 
-    mocker.patch(
-        'funkwhale_api.music.models.TrackFile.get_file_size',
-        return_value=2)
+    mocker.patch("funkwhale_api.music.models.TrackFile.get_file_size", return_value=2)
 
     c.fix_file_size(dry_run=False)
 
@@ -50,24 +49,26 @@ def test_fix_track_files_size(factories, mocker):
 
 
 def test_fix_track_files_mimetype(factories, mocker):
-    name = 'test.mp3'
-    mp3_path = os.path.join(DATA_DIR, 'test.mp3')
-    ogg_path = os.path.join(DATA_DIR, 'test.ogg')
-    tf1 = factories['music.TrackFile'](
+    name = "test.mp3"
+    mp3_path = os.path.join(DATA_DIR, "test.mp3")
+    ogg_path = os.path.join(DATA_DIR, "test.ogg")
+    tf1 = factories["music.TrackFile"](
         audio_file__from_path=mp3_path,
-        source='file://{}'.format(mp3_path),
-        mimetype='application/x-empty')
+        source="file://{}".format(mp3_path),
+        mimetype="application/x-empty",
+    )
 
     # this one already has a mimetype set, to it should not be updated
-    tf2 = factories['music.TrackFile'](
+    tf2 = factories["music.TrackFile"](
         audio_file__from_path=ogg_path,
-        source='file://{}'.format(ogg_path),
-        mimetype='audio/something')
+        source="file://{}".format(ogg_path),
+        mimetype="audio/something",
+    )
     c = fix_track_files.Command()
     c.fix_mimetypes(dry_run=False)
 
     tf1.refresh_from_db()
     tf2.refresh_from_db()
 
-    assert tf1.mimetype == 'audio/mpeg'
-    assert tf2.mimetype == 'audio/something'
+    assert tf1.mimetype == "audio/mpeg"
+    assert tf2.mimetype == "audio/something"
diff --git a/api/tests/music/test_import.py b/api/tests/music/test_import.py
index 8453dca840..9ea8c4ec85 100644
--- a/api/tests/music/test_import.py
+++ b/api/tests/music/test_import.py
@@ -12,56 +12,53 @@ DATA_DIR = os.path.dirname(os.path.abspath(__file__))
 
 
 def test_create_import_can_bind_to_request(
-        artists, albums, mocker, factories, superuser_api_client):
-    request = factories['requests.ImportRequest']()
+    artists, albums, mocker, factories, superuser_api_client
+):
+    request = factories["requests.ImportRequest"]()
 
-    mocker.patch('funkwhale_api.music.tasks.import_job_run')
+    mocker.patch("funkwhale_api.music.tasks.import_job_run")
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.artists.get',
-        return_value=artists['get']['soad'])
-    mocker.patch(
-        'funkwhale_api.musicbrainz.api.images.get_front',
-        return_value=b'')
+        "funkwhale_api.musicbrainz.api.artists.get", return_value=artists["get"]["soad"]
+    )
+    mocker.patch("funkwhale_api.musicbrainz.api.images.get_front", return_value=b"")
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.releases.get',
-        return_value=albums['get_with_includes']['hypnotize'])
+        "funkwhale_api.musicbrainz.api.releases.get",
+        return_value=albums["get_with_includes"]["hypnotize"],
+    )
     payload = {
-        'releaseId': '47ae093f-1607-49a3-be11-a15d335ccc94',
-        'importRequest': request.pk,
-        'tracks': [
+        "releaseId": "47ae093f-1607-49a3-be11-a15d335ccc94",
+        "importRequest": request.pk,
+        "tracks": [
             {
-                'mbid': '1968a9d6-8d92-4051-8f76-674e157b6eed',
-                'source': 'https://www.youtube.com/watch?v=1111111111',
+                "mbid": "1968a9d6-8d92-4051-8f76-674e157b6eed",
+                "source": "https://www.youtube.com/watch?v=1111111111",
             }
-        ]
+        ],
     }
-    url = reverse('api:v1:submit-album')
+    url = reverse("api:v1:submit-album")
     response = superuser_api_client.post(
-        url, json.dumps(payload), content_type='application/json')
-    batch = request.import_batches.latest('id')
+        url, json.dumps(payload), content_type="application/json"
+    )
+    batch = request.import_batches.latest("id")
 
     assert batch.import_request == request
 
 
 def test_import_job_from_federation_no_musicbrainz(factories, mocker):
     mocker.patch(
-        'funkwhale_api.music.utils.get_audio_file_data',
-        return_value={'bitrate': 24, 'length': 666})
-    mocker.patch(
-        'funkwhale_api.music.models.TrackFile.get_file_size',
-        return_value=42)
-    lt = factories['federation.LibraryTrack'](
-        artist_name='Hello',
-        album_title='World',
-        title='Ping',
+        "funkwhale_api.music.utils.get_audio_file_data",
+        return_value={"bitrate": 24, "length": 666},
+    )
+    mocker.patch("funkwhale_api.music.models.TrackFile.get_file_size", return_value=42)
+    lt = factories["federation.LibraryTrack"](
+        artist_name="Hello",
+        album_title="World",
+        title="Ping",
         metadata__length=42,
         metadata__bitrate=43,
         metadata__size=44,
     )
-    job = factories['music.ImportJob'](
-        federation=True,
-        library_track=lt,
-    )
+    job = factories["music.ImportJob"](federation=True, library_track=lt)
 
     tasks.import_job_run(import_job_id=job.pk)
     job.refresh_from_db()
@@ -72,25 +69,21 @@ def test_import_job_from_federation_no_musicbrainz(factories, mocker):
     assert tf.bitrate == 43
     assert tf.size == 44
     assert tf.library_track == job.library_track
-    assert tf.track.title == 'Ping'
-    assert tf.track.artist.name == 'Hello'
-    assert tf.track.album.title == 'World'
+    assert tf.track.title == "Ping"
+    assert tf.track.artist.name == "Hello"
+    assert tf.track.album.title == "World"
 
 
 def test_import_job_from_federation_musicbrainz_recording(factories, mocker):
-    t = factories['music.Track']()
+    t = factories["music.Track"]()
     track_from_api = mocker.patch(
-        'funkwhale_api.music.models.Track.get_or_create_from_api',
-        return_value=(t, True))
-    lt = factories['federation.LibraryTrack'](
-        metadata__recording__musicbrainz=True,
-        artist_name='Hello',
-        album_title='World',
+        "funkwhale_api.music.models.Track.get_or_create_from_api",
+        return_value=(t, True),
     )
-    job = factories['music.ImportJob'](
-        federation=True,
-        library_track=lt,
+    lt = factories["federation.LibraryTrack"](
+        metadata__recording__musicbrainz=True, artist_name="Hello", album_title="World"
     )
+    job = factories["music.ImportJob"](federation=True, library_track=lt)
 
     tasks.import_job_run(import_job_id=job.pk)
     job.refresh_from_db()
@@ -100,23 +93,20 @@ def test_import_job_from_federation_musicbrainz_recording(factories, mocker):
     assert tf.library_track == job.library_track
     assert tf.track == t
     track_from_api.assert_called_once_with(
-        mbid=lt.metadata['recording']['musicbrainz_id'])
+        mbid=lt.metadata["recording"]["musicbrainz_id"]
+    )
 
 
 def test_import_job_from_federation_musicbrainz_release(factories, mocker):
-    a = factories['music.Album']()
+    a = factories["music.Album"]()
     album_from_api = mocker.patch(
-        'funkwhale_api.music.models.Album.get_or_create_from_api',
-        return_value=(a, True))
-    lt = factories['federation.LibraryTrack'](
-        metadata__release__musicbrainz=True,
-        artist_name='Hello',
-        title='Ping',
+        "funkwhale_api.music.models.Album.get_or_create_from_api",
+        return_value=(a, True),
     )
-    job = factories['music.ImportJob'](
-        federation=True,
-        library_track=lt,
+    lt = factories["federation.LibraryTrack"](
+        metadata__release__musicbrainz=True, artist_name="Hello", title="Ping"
     )
+    job = factories["music.ImportJob"](federation=True, library_track=lt)
 
     tasks.import_job_run(import_job_id=job.pk)
     job.refresh_from_db()
@@ -124,28 +114,25 @@ def test_import_job_from_federation_musicbrainz_release(factories, mocker):
     tf = job.track_file
     assert tf.mimetype == lt.audio_mimetype
     assert tf.library_track == job.library_track
-    assert tf.track.title == 'Ping'
+    assert tf.track.title == "Ping"
     assert tf.track.artist == a.artist
     assert tf.track.album == a
 
     album_from_api.assert_called_once_with(
-        mbid=lt.metadata['release']['musicbrainz_id'])
+        mbid=lt.metadata["release"]["musicbrainz_id"]
+    )
 
 
 def test_import_job_from_federation_musicbrainz_artist(factories, mocker):
-    a = factories['music.Artist']()
+    a = factories["music.Artist"]()
     artist_from_api = mocker.patch(
-        'funkwhale_api.music.models.Artist.get_or_create_from_api',
-        return_value=(a, True))
-    lt = factories['federation.LibraryTrack'](
-        metadata__artist__musicbrainz=True,
-        album_title='World',
-        title='Ping',
+        "funkwhale_api.music.models.Artist.get_or_create_from_api",
+        return_value=(a, True),
     )
-    job = factories['music.ImportJob'](
-        federation=True,
-        library_track=lt,
+    lt = factories["federation.LibraryTrack"](
+        metadata__artist__musicbrainz=True, album_title="World", title="Ping"
     )
+    job = factories["music.ImportJob"](federation=True, library_track=lt)
 
     tasks.import_job_run(import_job_id=job.pk)
     job.refresh_from_db()
@@ -154,108 +141,102 @@ def test_import_job_from_federation_musicbrainz_artist(factories, mocker):
     assert tf.mimetype == lt.audio_mimetype
     assert tf.library_track == job.library_track
 
-    assert tf.track.title == 'Ping'
+    assert tf.track.title == "Ping"
     assert tf.track.artist == a
     assert tf.track.album.artist == a
-    assert tf.track.album.title == 'World'
+    assert tf.track.album.title == "World"
 
     artist_from_api.assert_called_once_with(
-        mbid=lt.metadata['artist']['musicbrainz_id'])
+        mbid=lt.metadata["artist"]["musicbrainz_id"]
+    )
 
 
-def test_import_job_run_triggers_notifies_followers(
-        factories, mocker, tmpfile):
+def test_import_job_run_triggers_notifies_followers(factories, mocker, tmpfile):
     mocker.patch(
-        'funkwhale_api.downloader.download',
-        return_value={'audio_file_path': tmpfile.name})
+        "funkwhale_api.downloader.download",
+        return_value={"audio_file_path": tmpfile.name},
+    )
     mocked_notify = mocker.patch(
-        'funkwhale_api.music.tasks.import_batch_notify_followers.delay')
-    batch = factories['music.ImportBatch']()
-    job = factories['music.ImportJob'](
-        finished=True, batch=batch)
-    track = factories['music.Track'](mbid=job.mbid)
+        "funkwhale_api.music.tasks.import_batch_notify_followers.delay"
+    )
+    batch = factories["music.ImportBatch"]()
+    job = factories["music.ImportJob"](finished=True, batch=batch)
+    track = factories["music.Track"](mbid=job.mbid)
 
     batch.update_status()
     batch.refresh_from_db()
 
-    assert batch.status == 'finished'
+    assert batch.status == "finished"
 
     mocked_notify.assert_called_once_with(import_batch_id=batch.pk)
 
 
 def test_import_batch_notifies_followers_skip_on_disabled_federation(
-        preferences, factories, mocker):
-    mocked_deliver = mocker.patch('funkwhale_api.federation.activity.deliver')
-    batch = factories['music.ImportBatch'](finished=True)
-    preferences['federation__enabled'] = False
+    preferences, factories, mocker
+):
+    mocked_deliver = mocker.patch("funkwhale_api.federation.activity.deliver")
+    batch = factories["music.ImportBatch"](finished=True)
+    preferences["federation__enabled"] = False
     tasks.import_batch_notify_followers(import_batch_id=batch.pk)
 
     mocked_deliver.assert_not_called()
 
 
-def test_import_batch_notifies_followers_skip_on_federation_import(
-        factories, mocker):
-    mocked_deliver = mocker.patch('funkwhale_api.federation.activity.deliver')
-    batch = factories['music.ImportBatch'](finished=True, federation=True)
+def test_import_batch_notifies_followers_skip_on_federation_import(factories, mocker):
+    mocked_deliver = mocker.patch("funkwhale_api.federation.activity.deliver")
+    batch = factories["music.ImportBatch"](finished=True, federation=True)
     tasks.import_batch_notify_followers(import_batch_id=batch.pk)
 
     mocked_deliver.assert_not_called()
 
 
-def test_import_batch_notifies_followers(
-        factories, mocker):
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
+def test_import_batch_notifies_followers(factories, mocker):
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
 
-    f1 = factories['federation.Follow'](approved=True, target=library_actor)
-    f2 = factories['federation.Follow'](approved=False, target=library_actor)
-    f3 = factories['federation.Follow']()
+    f1 = factories["federation.Follow"](approved=True, target=library_actor)
+    f2 = factories["federation.Follow"](approved=False, target=library_actor)
+    f3 = factories["federation.Follow"]()
 
-    mocked_deliver = mocker.patch('funkwhale_api.federation.activity.deliver')
-    batch = factories['music.ImportBatch']()
-    job1 = factories['music.ImportJob'](
-        finished=True, batch=batch)
-    job2 = factories['music.ImportJob'](
-        finished=True, federation=True, batch=batch)
-    job3 = factories['music.ImportJob'](
-        status='pending', batch=batch)
+    mocked_deliver = mocker.patch("funkwhale_api.federation.activity.deliver")
+    batch = factories["music.ImportBatch"]()
+    job1 = factories["music.ImportJob"](finished=True, batch=batch)
+    job2 = factories["music.ImportJob"](finished=True, federation=True, batch=batch)
+    job3 = factories["music.ImportJob"](status="pending", batch=batch)
 
-    batch.status = 'finished'
+    batch.status = "finished"
     batch.save()
     tasks.import_batch_notify_followers(import_batch_id=batch.pk)
 
     # only f1 match the requirements to be notified
     # and only job1 is a non federated track with finished import
     expected = {
-        '@context': federation_serializers.AP_CONTEXT,
-        'actor': library_actor.url,
-        'type': 'Create',
-        'id': batch.get_federation_url(),
-        'to': [f1.actor.url],
-        'object': federation_serializers.CollectionSerializer(
+        "@context": federation_serializers.AP_CONTEXT,
+        "actor": library_actor.url,
+        "type": "Create",
+        "id": batch.get_federation_url(),
+        "to": [f1.actor.url],
+        "object": federation_serializers.CollectionSerializer(
             {
-                'id': batch.get_federation_url(),
-                'items': [job1.track_file],
-                'actor': library_actor,
-                'item_serializer': federation_serializers.AudioSerializer
+                "id": batch.get_federation_url(),
+                "items": [job1.track_file],
+                "actor": library_actor,
+                "item_serializer": federation_serializers.AudioSerializer,
             }
-        ).data
+        ).data,
     }
 
     mocked_deliver.assert_called_once_with(
-        expected,
-        on_behalf_of=library_actor,
-        to=[f1.actor.url]
+        expected, on_behalf_of=library_actor, to=[f1.actor.url]
     )
 
 
 def test__do_import_in_place_mbid(factories, tmpfile):
-    path = os.path.join(DATA_DIR, 'test.ogg')
-    job = factories['music.ImportJob'](
-        in_place=True, source='file://{}'.format(path))
+    path = os.path.join(DATA_DIR, "test.ogg")
+    job = factories["music.ImportJob"](in_place=True, source="file://{}".format(path))
 
-    track = factories['music.Track'](mbid=job.mbid)
+    track = factories["music.Track"](mbid=job.mbid)
     tf = tasks._do_import(job, use_acoustid=False)
 
     assert bool(tf.audio_file) is False
-    assert tf.source == 'file://{}'.format(path)
-    assert tf.mimetype == 'audio/ogg'
+    assert tf.source == "file://{}".format(path)
+    assert tf.mimetype == "audio/ogg"
diff --git a/api/tests/music/test_lyrics.py b/api/tests/music/test_lyrics.py
index 3aee368c0e..c15e306373 100644
--- a/api/tests/music/test_lyrics.py
+++ b/api/tests/music/test_lyrics.py
@@ -8,20 +8,17 @@ from funkwhale_api.music import tasks
 from funkwhale_api.music import lyrics as lyrics_utils
 
 
-def test_works_import_lyrics_if_any(
-        lyricswiki_content, mocker, factories):
+def test_works_import_lyrics_if_any(lyricswiki_content, mocker, factories):
     mocker.patch(
-        'funkwhale_api.music.lyrics._get_html',
-        return_value=lyricswiki_content)
-    lyrics = factories['music.Lyrics'](
-        url='http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!')
+        "funkwhale_api.music.lyrics._get_html", return_value=lyricswiki_content
+    )
+    lyrics = factories["music.Lyrics"](
+        url="http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!"
+    )
 
     tasks.fetch_content(lyrics_id=lyrics.pk)
     lyrics.refresh_from_db()
-    self.assertIn(
-        'Grab a brush and put on a little makeup',
-        lyrics.content,
-    )
+    self.assertIn("Grab a brush and put on a little makeup", lyrics.content)
 
 
 def test_clean_content():
@@ -39,40 +36,38 @@ def test_markdown_rendering(factories):
     content = """Hello
 Is it me you're looking for?"""
 
-    l = factories['music.Lyrics'](content=content)
+    l = factories["music.Lyrics"](content=content)
 
     expected = "<p>Hello<br />\nIs it me you're looking for?</p>"
     assert expected == l.content_rendered
 
 
 def test_works_import_lyrics_if_any(
-        lyricswiki_content,
-        works,
-        tracks,
-        mocker,
-        factories,
-        logged_in_client):
+    lyricswiki_content, works, tracks, mocker, factories, logged_in_client
+):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.works.get',
-        return_value=works['get']['chop_suey'])
+        "funkwhale_api.musicbrainz.api.works.get",
+        return_value=works["get"]["chop_suey"],
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.recordings.get',
-        return_value=tracks['get']['chop_suey'])
+        "funkwhale_api.musicbrainz.api.recordings.get",
+        return_value=tracks["get"]["chop_suey"],
+    )
     mocker.patch(
-        'funkwhale_api.music.lyrics._get_html',
-        return_value=lyricswiki_content)
-    track = factories['music.Track'](
-        work=None,
-        mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
+        "funkwhale_api.music.lyrics._get_html", return_value=lyricswiki_content
+    )
+    track = factories["music.Track"](
+        work=None, mbid="07ca77cf-f513-4e9c-b190-d7e24bbad448"
+    )
 
-    url = reverse('api:v1:tracks-lyrics', kwargs={'pk': track.pk})
+    url = reverse("api:v1:tracks-lyrics", kwargs={"pk": track.pk})
     response = logged_in_client.get(url)
 
     assert response.status_code == 200
 
     track.refresh_from_db()
-    lyrics = models.Lyrics.objects.latest('id')
-    work = models.Work.objects.latest('id')
+    lyrics = models.Lyrics.objects.latest("id")
+    work = models.Work.objects.latest("id")
 
     assert track.work == work
     assert lyrics.work == work
diff --git a/api/tests/music/test_metadata.py b/api/tests/music/test_metadata.py
index a4f15b3355..f1e03ad128 100644
--- a/api/tests/music/test_metadata.py
+++ b/api/tests/music/test_metadata.py
@@ -8,104 +8,116 @@ from funkwhale_api.music import metadata
 DATA_DIR = os.path.dirname(os.path.abspath(__file__))
 
 
-@pytest.mark.parametrize('field,value', [
-    ('title', 'Peer Gynt Suite no. 1, op. 46: I. Morning'),
-    ('artist', 'Edvard Grieg'),
-    ('album', 'Peer Gynt Suite no. 1, op. 46'),
-    ('date', datetime.date(2012, 8, 15)),
-    ('track_number', 1),
-    ('musicbrainz_albumid', uuid.UUID('a766da8b-8336-47aa-a3ee-371cc41ccc75')),
-    ('musicbrainz_recordingid', uuid.UUID('bd21ac48-46d8-4e78-925f-d9cc2a294656')),
-    ('musicbrainz_artistid', uuid.UUID('013c8e5b-d72a-4cd3-8dee-6c64d6125823')),
-])
+@pytest.mark.parametrize(
+    "field,value",
+    [
+        ("title", "Peer Gynt Suite no. 1, op. 46: I. Morning"),
+        ("artist", "Edvard Grieg"),
+        ("album", "Peer Gynt Suite no. 1, op. 46"),
+        ("date", datetime.date(2012, 8, 15)),
+        ("track_number", 1),
+        ("musicbrainz_albumid", uuid.UUID("a766da8b-8336-47aa-a3ee-371cc41ccc75")),
+        ("musicbrainz_recordingid", uuid.UUID("bd21ac48-46d8-4e78-925f-d9cc2a294656")),
+        ("musicbrainz_artistid", uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823")),
+    ],
+)
 def test_can_get_metadata_from_ogg_file(field, value):
-    path = os.path.join(DATA_DIR, 'test.ogg')
+    path = os.path.join(DATA_DIR, "test.ogg")
     data = metadata.Metadata(path)
 
     assert data.get(field) == value
 
-@pytest.mark.parametrize('field,value', [
-    ('title', 'Drei Kreuze (dass wir hier sind)'),
-    ('artist', 'Die Toten Hosen'),
-    ('album', 'Ballast der Republik'),
-    ('date', datetime.date(2012, 5, 4)),
-    ('track_number', 1),
-    ('musicbrainz_albumid', uuid.UUID('1f0441ad-e609-446d-b355-809c445773cf')),
-    ('musicbrainz_recordingid', uuid.UUID('124d0150-8627-46bc-bc14-789a3bc960c8')),
-    ('musicbrainz_artistid', uuid.UUID('c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1')),
-])
+
+@pytest.mark.parametrize(
+    "field,value",
+    [
+        ("title", "Drei Kreuze (dass wir hier sind)"),
+        ("artist", "Die Toten Hosen"),
+        ("album", "Ballast der Republik"),
+        ("date", datetime.date(2012, 5, 4)),
+        ("track_number", 1),
+        ("musicbrainz_albumid", uuid.UUID("1f0441ad-e609-446d-b355-809c445773cf")),
+        ("musicbrainz_recordingid", uuid.UUID("124d0150-8627-46bc-bc14-789a3bc960c8")),
+        ("musicbrainz_artistid", uuid.UUID("c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1")),
+    ],
+)
 def test_can_get_metadata_from_ogg_theora_file(field, value):
-    path = os.path.join(DATA_DIR, 'test_theora.ogg')
+    path = os.path.join(DATA_DIR, "test_theora.ogg")
     data = metadata.Metadata(path)
 
     assert data.get(field) == value
 
 
-@pytest.mark.parametrize('field,value', [
-    ('title', 'Bend'),
-    ('artist', 'Bindrpilot'),
-    ('album', 'You Can\'t Stop Da Funk'),
-    ('date', datetime.date(2006, 2, 7)),
-    ('track_number', 2),
-    ('musicbrainz_albumid', uuid.UUID('ce40cdb1-a562-4fd8-a269-9269f98d4124')),
-    ('musicbrainz_recordingid', uuid.UUID('f269d497-1cc0-4ae4-a0c4-157ec7d73fcb')),
-    ('musicbrainz_artistid', uuid.UUID('9c6bddde-6228-4d9f-ad0d-03f6fcb19e13')),
-])
+@pytest.mark.parametrize(
+    "field,value",
+    [
+        ("title", "Bend"),
+        ("artist", "Bindrpilot"),
+        ("album", "You Can't Stop Da Funk"),
+        ("date", datetime.date(2006, 2, 7)),
+        ("track_number", 2),
+        ("musicbrainz_albumid", uuid.UUID("ce40cdb1-a562-4fd8-a269-9269f98d4124")),
+        ("musicbrainz_recordingid", uuid.UUID("f269d497-1cc0-4ae4-a0c4-157ec7d73fcb")),
+        ("musicbrainz_artistid", uuid.UUID("9c6bddde-6228-4d9f-ad0d-03f6fcb19e13")),
+    ],
+)
 def test_can_get_metadata_from_id3_mp3_file(field, value):
-    path = os.path.join(DATA_DIR, 'test.mp3')
+    path = os.path.join(DATA_DIR, "test.mp3")
     data = metadata.Metadata(path)
 
     assert data.get(field) == value
 
 
-@pytest.mark.parametrize('name', ['test.mp3', 'sample.flac'])
+@pytest.mark.parametrize("name", ["test.mp3", "sample.flac"])
 def test_can_get_pictures(name):
     path = os.path.join(DATA_DIR, name)
     data = metadata.Metadata(path)
 
-    pictures = data.get('pictures')
+    pictures = data.get("pictures")
     assert len(pictures) == 1
-    cover_data = data.get_picture('cover_front')
-    assert cover_data['mimetype'].startswith('image/')
-    assert len(cover_data['content']) > 0
-    assert type(cover_data['content']) == bytes
-    assert type(cover_data['description']) == str
-
-
-@pytest.mark.parametrize('field,value', [
-    ('title', '999,999'),
-    ('artist', 'Nine Inch Nails'),
-    ('album', 'The Slip'),
-    ('date', datetime.date(2008, 5, 5)),
-    ('track_number', 1),
-    ('musicbrainz_albumid', uuid.UUID('12b57d46-a192-499e-a91f-7da66790a1c1')),
-    ('musicbrainz_recordingid', uuid.UUID('30f3f33e-8d0c-4e69-8539-cbd701d18f28')),
-    ('musicbrainz_artistid', uuid.UUID('b7ffd2af-418f-4be2-bdd1-22f8b48613da')),
-])
+    cover_data = data.get_picture("cover_front")
+    assert cover_data["mimetype"].startswith("image/")
+    assert len(cover_data["content"]) > 0
+    assert type(cover_data["content"]) == bytes
+    assert type(cover_data["description"]) == str
+
+
+@pytest.mark.parametrize(
+    "field,value",
+    [
+        ("title", "999,999"),
+        ("artist", "Nine Inch Nails"),
+        ("album", "The Slip"),
+        ("date", datetime.date(2008, 5, 5)),
+        ("track_number", 1),
+        ("musicbrainz_albumid", uuid.UUID("12b57d46-a192-499e-a91f-7da66790a1c1")),
+        ("musicbrainz_recordingid", uuid.UUID("30f3f33e-8d0c-4e69-8539-cbd701d18f28")),
+        ("musicbrainz_artistid", uuid.UUID("b7ffd2af-418f-4be2-bdd1-22f8b48613da")),
+    ],
+)
 def test_can_get_metadata_from_flac_file(field, value):
-    path = os.path.join(DATA_DIR, 'sample.flac')
+    path = os.path.join(DATA_DIR, "sample.flac")
     data = metadata.Metadata(path)
 
     assert data.get(field) == value
 
 
 def test_can_get_metadata_from_flac_file_not_crash_if_empty():
-    path = os.path.join(DATA_DIR, 'sample.flac')
+    path = os.path.join(DATA_DIR, "sample.flac")
     data = metadata.Metadata(path)
 
     with pytest.raises(metadata.TagNotFound):
-        data.get('test')
+        data.get("test")
 
 
-@pytest.mark.parametrize('field_name', [
-    'musicbrainz_artistid',
-    'musicbrainz_albumid',
-    'musicbrainz_recordingid',
-])
+@pytest.mark.parametrize(
+    "field_name",
+    ["musicbrainz_artistid", "musicbrainz_albumid", "musicbrainz_recordingid"],
+)
 def test_mbid_clean_keeps_only_first(field_name):
     u1 = str(uuid.uuid4())
     u2 = str(uuid.uuid4())
     field = metadata.VALIDATION[field_name]
-    result = field.to_python('/'.join([u1, u2]))
+    result = field.to_python("/".join([u1, u2]))
 
     assert str(result) == u1
diff --git a/api/tests/music/test_models.py b/api/tests/music/test_models.py
index 0ef54eb668..f81dd433fd 100644
--- a/api/tests/music/test_models.py
+++ b/api/tests/music/test_models.py
@@ -9,7 +9,7 @@ DATA_DIR = os.path.dirname(os.path.abspath(__file__))
 
 
 def test_can_store_release_group_id_on_album(factories):
-    album = factories['music.Album']()
+    album = factories["music.Album"]()
     assert album.release_group_id is not None
 
 
@@ -21,7 +21,7 @@ def test_import_album_stores_release_group(factories):
                     "disambiguation": "George Shaw",
                     "id": "62c3befb-6366-4585-b256-809472333801",
                     "name": "Adhesive Wombat",
-                    "sort-name": "Wombat, Adhesive"
+                    "sort-name": "Wombat, Adhesive",
                 }
             }
         ],
@@ -31,137 +31,134 @@ def test_import_album_stores_release_group(factories):
         "id": "a50d2a81-2a50-484d-9cb4-b9f6833f583e",
         "status": "Official",
         "title": "Marsupial Madness",
-        'release-group': {'id': '447b4979-2178-405c-bfe6-46bf0b09e6c7'}
+        "release-group": {"id": "447b4979-2178-405c-bfe6-46bf0b09e6c7"},
     }
-    artist = factories['music.Artist'](
-        mbid=album_data['artist-credit'][0]['artist']['id']
+    artist = factories["music.Artist"](
+        mbid=album_data["artist-credit"][0]["artist"]["id"]
     )
     cleaned_data = models.Album.clean_musicbrainz_data(album_data)
     album = importers.load(models.Album, cleaned_data, album_data, import_hooks=[])
 
-    assert album.release_group_id == album_data['release-group']['id']
+    assert album.release_group_id == album_data["release-group"]["id"]
     assert album.artist == artist
 
 
 def test_import_track_from_release(factories, mocker):
-    album = factories['music.Album'](
-        mbid='430347cb-0879-3113-9fde-c75b658c298e')
+    album = factories["music.Album"](mbid="430347cb-0879-3113-9fde-c75b658c298e")
     album_data = {
-        'release': {
-            'id': album.mbid,
-            'title': 'Daydream Nation',
-            'status': 'Official',
-            'medium-count': 1,
-            'medium-list': [
+        "release": {
+            "id": album.mbid,
+            "title": "Daydream Nation",
+            "status": "Official",
+            "medium-count": 1,
+            "medium-list": [
                 {
-                    'position': '1',
-                    'format': 'CD',
-                    'track-list': [
+                    "position": "1",
+                    "format": "CD",
+                    "track-list": [
                         {
-                            'id': '03baca8b-855a-3c05-8f3d-d3235287d84d',
-                            'position': '4',
-                            'number': '4',
-                            'length': '417973',
-                            'recording': {
-                                'id': '2109e376-132b-40ad-b993-2bb6812e19d4',
-                                'title': 'Teen Age Riot',
-                                'length': '417973'},
-                            'track_or_recording_length': '417973'
+                            "id": "03baca8b-855a-3c05-8f3d-d3235287d84d",
+                            "position": "4",
+                            "number": "4",
+                            "length": "417973",
+                            "recording": {
+                                "id": "2109e376-132b-40ad-b993-2bb6812e19d4",
+                                "title": "Teen Age Riot",
+                                "length": "417973",
+                            },
+                            "track_or_recording_length": "417973",
                         }
                     ],
-                    'track-count': 1
+                    "track-count": 1,
                 }
             ],
         }
     }
     mocked_get = mocker.patch(
-        'funkwhale_api.musicbrainz.api.releases.get',
-        return_value=album_data)
-    track_data = album_data['release']['medium-list'][0]['track-list'][0]
+        "funkwhale_api.musicbrainz.api.releases.get", return_value=album_data
+    )
+    track_data = album_data["release"]["medium-list"][0]["track-list"][0]
     track = models.Track.get_or_create_from_release(
-        '430347cb-0879-3113-9fde-c75b658c298e',
-        track_data['recording']['id'],
+        "430347cb-0879-3113-9fde-c75b658c298e", track_data["recording"]["id"]
     )[0]
-    mocked_get.assert_called_once_with(
-        album.mbid, includes=models.Album.api_includes)
-    assert track.title == track_data['recording']['title']
-    assert track.mbid == track_data['recording']['id']
+    mocked_get.assert_called_once_with(album.mbid, includes=models.Album.api_includes)
+    assert track.title == track_data["recording"]["title"]
+    assert track.mbid == track_data["recording"]["id"]
     assert track.album == album
     assert track.artist == album.artist
-    assert track.position == int(track_data['position'])
+    assert track.position == int(track_data["position"])
+
 
 def test_import_job_is_bound_to_track_file(factories, mocker):
-    track = factories['music.Track']()
-    job = factories['music.ImportJob'](mbid=track.mbid)
+    track = factories["music.Track"]()
+    job = factories["music.ImportJob"](mbid=track.mbid)
 
-    mocker.patch('funkwhale_api.music.models.TrackFile.download_file')
+    mocker.patch("funkwhale_api.music.models.TrackFile.download_file")
     tasks.import_job_run(import_job_id=job.pk)
     job.refresh_from_db()
     assert job.track_file.track == track
 
 
-@pytest.mark.parametrize('status', ['pending', 'errored', 'finished'])
-def test_saving_job_updates_batch_status(status,factories, mocker):
-    batch = factories['music.ImportBatch']()
+@pytest.mark.parametrize("status", ["pending", "errored", "finished"])
+def test_saving_job_updates_batch_status(status, factories, mocker):
+    batch = factories["music.ImportBatch"]()
 
-    assert batch.status == 'pending'
+    assert batch.status == "pending"
 
-    job = factories['music.ImportJob'](batch=batch, status=status)
+    job = factories["music.ImportJob"](batch=batch, status=status)
 
     batch.refresh_from_db()
 
     assert batch.status == status
 
 
-@pytest.mark.parametrize('extention,mimetype', [
-    ('ogg', 'audio/ogg'),
-    ('mp3', 'audio/mpeg'),
-])
+@pytest.mark.parametrize(
+    "extention,mimetype", [("ogg", "audio/ogg"), ("mp3", "audio/mpeg")]
+)
 def test_audio_track_mime_type(extention, mimetype, factories):
 
-    name = '.'.join(['test', extention])
+    name = ".".join(["test", extention])
     path = os.path.join(DATA_DIR, name)
-    tf = factories['music.TrackFile'](audio_file__from_path=path)
+    tf = factories["music.TrackFile"](audio_file__from_path=path)
 
     assert tf.mimetype == mimetype
 
 
 def test_track_file_file_name(factories):
-    name = 'test.mp3'
+    name = "test.mp3"
     path = os.path.join(DATA_DIR, name)
-    tf = factories['music.TrackFile'](audio_file__from_path=path)
+    tf = factories["music.TrackFile"](audio_file__from_path=path)
 
-    assert tf.filename == tf.track.full_name + '.mp3'
+    assert tf.filename == tf.track.full_name + ".mp3"
 
 
 def test_track_get_file_size(factories):
-    name = 'test.mp3'
+    name = "test.mp3"
     path = os.path.join(DATA_DIR, name)
-    tf = factories['music.TrackFile'](audio_file__from_path=path)
+    tf = factories["music.TrackFile"](audio_file__from_path=path)
 
     assert tf.get_file_size() == 297745
 
 
 def test_track_get_file_size_federation(factories):
-    tf = factories['music.TrackFile'](
-        federation=True,
-        library_track__with_audio_file=True)
+    tf = factories["music.TrackFile"](
+        federation=True, library_track__with_audio_file=True
+    )
 
     assert tf.get_file_size() == tf.library_track.audio_file.size
 
 
 def test_track_get_file_size_in_place(factories):
-    name = 'test.mp3'
+    name = "test.mp3"
     path = os.path.join(DATA_DIR, name)
-    tf = factories['music.TrackFile'](
-        in_place=True, source='file://{}'.format(path))
+    tf = factories["music.TrackFile"](in_place=True, source="file://{}".format(path))
 
     assert tf.get_file_size() == 297745
 
 
 def test_album_get_image_content(factories):
-    album = factories['music.Album']()
-    album.get_image(data={'content': b'test', 'mimetype':'image/jpeg'})
+    album = factories["music.Album"]()
+    album.get_image(data={"content": b"test", "mimetype": "image/jpeg"})
     album.refresh_from_db()
 
-    assert album.cover.read() == b'test'
+    assert album.cover.read() == b"test"
diff --git a/api/tests/music/test_music.py b/api/tests/music/test_music.py
index 4162912e4f..391af26cbf 100644
--- a/api/tests/music/test_music.py
+++ b/api/tests/music/test_music.py
@@ -5,121 +5,136 @@ import datetime
 
 def test_can_create_artist_from_api(artists, mocker, db):
     mocker.patch(
-        'musicbrainzngs.search_artists',
-        return_value=artists['search']['adhesive_wombat'])
+        "musicbrainzngs.search_artists",
+        return_value=artists["search"]["adhesive_wombat"],
+    )
     artist = models.Artist.create_from_api(query="Adhesive wombat")
-    data = models.Artist.api.search(query='Adhesive wombat')['artist-list'][0]
+    data = models.Artist.api.search(query="Adhesive wombat")["artist-list"][0]
 
-    assert int(data['ext:score']), 100
-    assert data['id'], '62c3befb-6366-4585-b256-809472333801'
-    assert artist.mbid, data['id']
-    assert artist.name, 'Adhesive Wombat'
+    assert int(data["ext:score"]), 100
+    assert data["id"], "62c3befb-6366-4585-b256-809472333801"
+    assert artist.mbid, data["id"]
+    assert artist.name, "Adhesive Wombat"
 
 
 def test_can_create_album_from_api(artists, albums, mocker, db):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.releases.search',
-        return_value=albums['search']['hypnotize'])
+        "funkwhale_api.musicbrainz.api.releases.search",
+        return_value=albums["search"]["hypnotize"],
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.artists.get',
-        return_value=artists['get']['soad'])
-    album = models.Album.create_from_api(query="Hypnotize", artist='system of a down', type='album')
-    data = models.Album.api.search(query='Hypnotize', artist='system of a down', type='album')['release-list'][0]
-
-    assert album.mbid, data['id']
-    assert album.title, 'Hypnotize'
+        "funkwhale_api.musicbrainz.api.artists.get", return_value=artists["get"]["soad"]
+    )
+    album = models.Album.create_from_api(
+        query="Hypnotize", artist="system of a down", type="album"
+    )
+    data = models.Album.api.search(
+        query="Hypnotize", artist="system of a down", type="album"
+    )["release-list"][0]
+
+    assert album.mbid, data["id"]
+    assert album.title, "Hypnotize"
     with pytest.raises(ValueError):
         assert album.cover.path is not None
     assert album.release_date, datetime.date(2005, 1, 1)
-    assert album.artist.name, 'System of a Down'
-    assert album.artist.mbid, data['artist-credit'][0]['artist']['id']
+    assert album.artist.name, "System of a Down"
+    assert album.artist.mbid, data["artist-credit"][0]["artist"]["id"]
 
 
 def test_can_create_track_from_api(artists, albums, tracks, mocker, db):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.artists.get',
-        return_value=artists['get']['adhesive_wombat'])
+        "funkwhale_api.musicbrainz.api.artists.get",
+        return_value=artists["get"]["adhesive_wombat"],
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.releases.get',
-        return_value=albums['get']['marsupial'])
+        "funkwhale_api.musicbrainz.api.releases.get",
+        return_value=albums["get"]["marsupial"],
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.recordings.search',
-        return_value=tracks['search']['8bitadventures'])
+        "funkwhale_api.musicbrainz.api.recordings.search",
+        return_value=tracks["search"]["8bitadventures"],
+    )
     track = models.Track.create_from_api(query="8-bit adventure")
-    data = models.Track.api.search(query='8-bit adventure')['recording-list'][0]
-    assert int(data['ext:score']) == 100
-    assert data['id'] == '9968a9d6-8d92-4051-8f76-674e157b6eed'
-    assert track.mbid == data['id']
+    data = models.Track.api.search(query="8-bit adventure")["recording-list"][0]
+    assert int(data["ext:score"]) == 100
+    assert data["id"] == "9968a9d6-8d92-4051-8f76-674e157b6eed"
+    assert track.mbid == data["id"]
     assert track.artist.pk is not None
-    assert str(track.artist.mbid) == '62c3befb-6366-4585-b256-809472333801'
-    assert track.artist.name == 'Adhesive Wombat'
-    assert str(track.album.mbid) == 'a50d2a81-2a50-484d-9cb4-b9f6833f583e'
-    assert track.album.title == 'Marsupial Madness'
+    assert str(track.artist.mbid) == "62c3befb-6366-4585-b256-809472333801"
+    assert track.artist.name == "Adhesive Wombat"
+    assert str(track.album.mbid) == "a50d2a81-2a50-484d-9cb4-b9f6833f583e"
+    assert track.album.title == "Marsupial Madness"
 
 
 def test_can_create_track_from_api_with_corresponding_tags(
-        artists, albums, tracks, mocker, db):
+    artists, albums, tracks, mocker, db
+):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.artists.get',
-        return_value=artists['get']['adhesive_wombat'])
+        "funkwhale_api.musicbrainz.api.artists.get",
+        return_value=artists["get"]["adhesive_wombat"],
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.releases.get',
-        return_value=albums['get']['marsupial'])
+        "funkwhale_api.musicbrainz.api.releases.get",
+        return_value=albums["get"]["marsupial"],
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.recordings.get',
-        return_value=tracks['get']['8bitadventures'])
-    track = models.Track.create_from_api(id='9968a9d6-8d92-4051-8f76-674e157b6eed')
-    expected_tags = ['techno', 'good-music']
+        "funkwhale_api.musicbrainz.api.recordings.get",
+        return_value=tracks["get"]["8bitadventures"],
+    )
+    track = models.Track.create_from_api(id="9968a9d6-8d92-4051-8f76-674e157b6eed")
+    expected_tags = ["techno", "good-music"]
     track_tags = [tag.slug for tag in track.tags.all()]
     for tag in expected_tags:
         assert tag in track_tags
 
 
-def test_can_get_or_create_track_from_api(
-        artists, albums, tracks, mocker, db):
+def test_can_get_or_create_track_from_api(artists, albums, tracks, mocker, db):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.artists.get',
-        return_value=artists['get']['adhesive_wombat'])
+        "funkwhale_api.musicbrainz.api.artists.get",
+        return_value=artists["get"]["adhesive_wombat"],
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.releases.get',
-        return_value=albums['get']['marsupial'])
+        "funkwhale_api.musicbrainz.api.releases.get",
+        return_value=albums["get"]["marsupial"],
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.recordings.search',
-        return_value=tracks['search']['8bitadventures'])
+        "funkwhale_api.musicbrainz.api.recordings.search",
+        return_value=tracks["search"]["8bitadventures"],
+    )
     track = models.Track.create_from_api(query="8-bit adventure")
-    data = models.Track.api.search(query='8-bit adventure')['recording-list'][0]
-    assert int(data['ext:score']) == 100
-    assert data['id'] == '9968a9d6-8d92-4051-8f76-674e157b6eed'
-    assert track.mbid == data['id']
+    data = models.Track.api.search(query="8-bit adventure")["recording-list"][0]
+    assert int(data["ext:score"]) == 100
+    assert data["id"] == "9968a9d6-8d92-4051-8f76-674e157b6eed"
+    assert track.mbid == data["id"]
     assert track.artist.pk is not None
-    assert str(track.artist.mbid) == '62c3befb-6366-4585-b256-809472333801'
-    assert track.artist.name == 'Adhesive Wombat'
+    assert str(track.artist.mbid) == "62c3befb-6366-4585-b256-809472333801"
+    assert track.artist.name == "Adhesive Wombat"
 
-    track2, created = models.Track.get_or_create_from_api(mbid=data['id'])
+    track2, created = models.Track.get_or_create_from_api(mbid=data["id"])
     assert not created
     assert track == track2
 
 
 def test_album_tags_deduced_from_tracks_tags(factories, django_assert_num_queries):
-    tag = factories['taggit.Tag']()
-    album = factories['music.Album']()
-    tracks = factories['music.Track'].create_batch(
-        5, album=album, tags=[tag])
+    tag = factories["taggit.Tag"]()
+    album = factories["music.Album"]()
+    tracks = factories["music.Track"].create_batch(5, album=album, tags=[tag])
 
-    album = models.Album.objects.prefetch_related('tracks__tags').get(pk=album.pk)
+    album = models.Album.objects.prefetch_related("tracks__tags").get(pk=album.pk)
 
     with django_assert_num_queries(0):
         assert tag in album.tags
 
 
 def test_artist_tags_deduced_from_album_tags(factories, django_assert_num_queries):
-    tag = factories['taggit.Tag']()
-    album = factories['music.Album']()
+    tag = factories["taggit.Tag"]()
+    album = factories["music.Album"]()
     artist = album.artist
-    tracks = factories['music.Track'].create_batch(
-        5, album=album, tags=[tag])
+    tracks = factories["music.Track"].create_batch(5, album=album, tags=[tag])
 
-    artist = models.Artist.objects.prefetch_related('albums__tracks__tags').get(pk=artist.pk)
+    artist = models.Artist.objects.prefetch_related("albums__tracks__tags").get(
+        pk=artist.pk
+    )
 
     with django_assert_num_queries(0):
         assert tag in artist.tags
@@ -127,10 +142,10 @@ def test_artist_tags_deduced_from_album_tags(factories, django_assert_num_querie
 
 def test_can_download_image_file_for_album(binary_cover, mocker, factories):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.images.get_front',
-        return_value=binary_cover)
+        "funkwhale_api.musicbrainz.api.images.get_front", return_value=binary_cover
+    )
     # client._api.get_image_front('55ea4f82-b42b-423e-a0e5-290ccdf443ed')
-    album = factories['music.Album'](mbid='55ea4f82-b42b-423e-a0e5-290ccdf443ed')
+    album = factories["music.Album"](mbid="55ea4f82-b42b-423e-a0e5-290ccdf443ed")
     album.get_image()
     album.save()
 
diff --git a/api/tests/music/test_permissions.py b/api/tests/music/test_permissions.py
index 825d1731dd..5f73a361e0 100644
--- a/api/tests/music/test_permissions.py
+++ b/api/tests/music/test_permissions.py
@@ -5,58 +5,56 @@ from funkwhale_api.music import permissions
 
 
 def test_list_permission_no_protect(preferences, anonymous_user, api_request):
-    preferences['common__api_authentication_required'] = False
+    preferences["common__api_authentication_required"] = False
     view = APIView.as_view()
     permission = permissions.Listen()
-    request = api_request.get('/')
+    request = api_request.get("/")
     assert permission.has_permission(request, view) is True
 
 
-def test_list_permission_protect_authenticated(
-        factories, api_request, preferences):
-    preferences['common__api_authentication_required'] = True
-    user = factories['users.User']()
+def test_list_permission_protect_authenticated(factories, api_request, preferences):
+    preferences["common__api_authentication_required"] = True
+    user = factories["users.User"]()
     view = APIView.as_view()
     permission = permissions.Listen()
-    request = api_request.get('/')
-    setattr(request, 'user', user)
+    request = api_request.get("/")
+    setattr(request, "user", user)
     assert permission.has_permission(request, view) is True
 
 
 def test_list_permission_protect_not_following_actor(
-        factories, api_request, preferences):
-    preferences['common__api_authentication_required'] = True
-    actor = factories['federation.Actor']()
+    factories, api_request, preferences
+):
+    preferences["common__api_authentication_required"] = True
+    actor = factories["federation.Actor"]()
     view = APIView.as_view()
     permission = permissions.Listen()
-    request = api_request.get('/')
-    setattr(request, 'actor', actor)
+    request = api_request.get("/")
+    setattr(request, "actor", actor)
     assert permission.has_permission(request, view) is False
 
 
-def test_list_permission_protect_following_actor(
-        factories, api_request, preferences):
-    preferences['common__api_authentication_required'] = True
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    follow = factories['federation.Follow'](
-        approved=True, target=library_actor)
+def test_list_permission_protect_following_actor(factories, api_request, preferences):
+    preferences["common__api_authentication_required"] = True
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    follow = factories["federation.Follow"](approved=True, target=library_actor)
     view = APIView.as_view()
     permission = permissions.Listen()
-    request = api_request.get('/')
-    setattr(request, 'actor', follow.actor)
+    request = api_request.get("/")
+    setattr(request, "actor", follow.actor)
 
     assert permission.has_permission(request, view) is True
 
 
 def test_list_permission_protect_following_actor_not_approved(
-        factories, api_request, preferences):
-    preferences['common__api_authentication_required'] = True
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    follow = factories['federation.Follow'](
-        approved=False, target=library_actor)
+    factories, api_request, preferences
+):
+    preferences["common__api_authentication_required"] = True
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    follow = factories["federation.Follow"](approved=False, target=library_actor)
     view = APIView.as_view()
     permission = permissions.Listen()
-    request = api_request.get('/')
-    setattr(request, 'actor', follow.actor)
+    request = api_request.get("/")
+    setattr(request, "actor", follow.actor)
 
     assert permission.has_permission(request, view) is False
diff --git a/api/tests/music/test_serializers.py b/api/tests/music/test_serializers.py
index fa22ceceeb..9402e1459a 100644
--- a/api/tests/music/test_serializers.py
+++ b/api/tests/music/test_serializers.py
@@ -2,18 +2,18 @@ from funkwhale_api.music import serializers
 
 
 def test_artist_album_serializer(factories, to_api_date):
-    track = factories['music.Track']()
+    track = factories["music.Track"]()
     album = track.album
     album = album.__class__.objects.with_tracks_count().get(pk=album.pk)
     expected = {
-        'id': album.id,
-        'mbid': str(album.mbid),
-        'title': album.title,
-        'artist': album.artist.id,
-        'creation_date': to_api_date(album.creation_date),
-        'tracks_count': 1,
-        'cover': album.cover.url,
-        'release_date': to_api_date(album.release_date),
+        "id": album.id,
+        "mbid": str(album.mbid),
+        "title": album.title,
+        "artist": album.artist.id,
+        "creation_date": to_api_date(album.creation_date),
+        "tracks_count": 1,
+        "cover": album.cover.url,
+        "release_date": to_api_date(album.release_date),
     }
     serializer = serializers.ArtistAlbumSerializer(album)
 
@@ -21,79 +21,72 @@ def test_artist_album_serializer(factories, to_api_date):
 
 
 def test_artist_with_albums_serializer(factories, to_api_date):
-    track = factories['music.Track']()
+    track = factories["music.Track"]()
     artist = track.artist
     artist = artist.__class__.objects.with_albums().get(pk=artist.pk)
     album = list(artist.albums.all())[0]
 
     expected = {
-        'id': artist.id,
-        'mbid': str(artist.mbid),
-        'name': artist.name,
-        'creation_date': to_api_date(artist.creation_date),
-        'albums': [
-            serializers.ArtistAlbumSerializer(album).data
-        ]
+        "id": artist.id,
+        "mbid": str(artist.mbid),
+        "name": artist.name,
+        "creation_date": to_api_date(artist.creation_date),
+        "albums": [serializers.ArtistAlbumSerializer(album).data],
     }
     serializer = serializers.ArtistWithAlbumsSerializer(artist)
     assert serializer.data == expected
 
 
 def test_album_track_serializer(factories, to_api_date):
-    tf = factories['music.TrackFile']()
+    tf = factories["music.TrackFile"]()
     track = tf.track
 
     expected = {
-        'id': track.id,
-        'artist': track.artist.id,
-        'album': track.album.id,
-        'mbid': str(track.mbid),
-        'title': track.title,
-        'position': track.position,
-        'creation_date': to_api_date(track.creation_date),
-        'files': [
-            serializers.TrackFileSerializer(tf).data
-        ]
+        "id": track.id,
+        "artist": track.artist.id,
+        "album": track.album.id,
+        "mbid": str(track.mbid),
+        "title": track.title,
+        "position": track.position,
+        "creation_date": to_api_date(track.creation_date),
+        "files": [serializers.TrackFileSerializer(tf).data],
     }
     serializer = serializers.AlbumTrackSerializer(track)
     assert serializer.data == expected
 
 
 def test_track_file_serializer(factories, to_api_date):
-    tf = factories['music.TrackFile']()
+    tf = factories["music.TrackFile"]()
 
     expected = {
-        'id': tf.id,
-        'path': tf.path,
-        'source': tf.source,
-        'filename': tf.filename,
-        'mimetype': tf.mimetype,
-        'track': tf.track.pk,
-        'duration': tf.duration,
-        'mimetype': tf.mimetype,
-        'bitrate': tf.bitrate,
-        'size': tf.size,
+        "id": tf.id,
+        "path": tf.path,
+        "source": tf.source,
+        "filename": tf.filename,
+        "mimetype": tf.mimetype,
+        "track": tf.track.pk,
+        "duration": tf.duration,
+        "mimetype": tf.mimetype,
+        "bitrate": tf.bitrate,
+        "size": tf.size,
     }
     serializer = serializers.TrackFileSerializer(tf)
     assert serializer.data == expected
 
 
 def test_album_serializer(factories, to_api_date):
-    track1 = factories['music.Track'](position=2)
-    track2 = factories['music.Track'](position=1, album=track1.album)
+    track1 = factories["music.Track"](position=2)
+    track2 = factories["music.Track"](position=1, album=track1.album)
     album = track1.album
     expected = {
-        'id': album.id,
-        'mbid': str(album.mbid),
-        'title': album.title,
-        'artist': serializers.ArtistSimpleSerializer(album.artist).data,
-        'creation_date': to_api_date(album.creation_date),
-        'cover': album.cover.url,
-        'release_date': to_api_date(album.release_date),
-        'tracks': serializers.AlbumTrackSerializer(
-            [track2, track1],
-            many=True
-        ).data
+        "id": album.id,
+        "mbid": str(album.mbid),
+        "title": album.title,
+        "artist": serializers.ArtistSimpleSerializer(album.artist).data,
+        "creation_date": to_api_date(album.creation_date),
+        "cover": album.cover.url,
+        "release_date": to_api_date(album.release_date),
+        "tracks": serializers.AlbumTrackSerializer([track2, track1], many=True).data,
     }
     serializer = serializers.AlbumSerializer(album)
 
@@ -101,21 +94,19 @@ def test_album_serializer(factories, to_api_date):
 
 
 def test_track_serializer(factories, to_api_date):
-    tf = factories['music.TrackFile']()
+    tf = factories["music.TrackFile"]()
     track = tf.track
 
     expected = {
-        'id': track.id,
-        'artist': serializers.ArtistSimpleSerializer(track.artist).data,
-        'album': serializers.TrackAlbumSerializer(track.album).data,
-        'mbid': str(track.mbid),
-        'title': track.title,
-        'position': track.position,
-        'creation_date': to_api_date(track.creation_date),
-        'lyrics': track.get_lyrics_url(),
-        'files': [
-            serializers.TrackFileSerializer(tf).data
-        ]
+        "id": track.id,
+        "artist": serializers.ArtistSimpleSerializer(track.artist).data,
+        "album": serializers.TrackAlbumSerializer(track.album).data,
+        "mbid": str(track.mbid),
+        "title": track.title,
+        "position": track.position,
+        "creation_date": to_api_date(track.creation_date),
+        "lyrics": track.get_lyrics_url(),
+        "files": [serializers.TrackFileSerializer(tf).data],
     }
     serializer = serializers.TrackSerializer(track)
     assert serializer.data == expected
diff --git a/api/tests/music/test_tasks.py b/api/tests/music/test_tasks.py
index 77245e204e..1d35e75e29 100644
--- a/api/tests/music/test_tasks.py
+++ b/api/tests/music/test_tasks.py
@@ -8,39 +8,45 @@ DATA_DIR = os.path.dirname(os.path.abspath(__file__))
 
 
 def test_set_acoustid_on_track_file(factories, mocker, preferences):
-    preferences['providers_acoustid__api_key'] = 'test'
-    track_file = factories['music.TrackFile'](acoustid_track_id=None)
-    id = 'e475bf79-c1ce-4441-bed7-1e33f226c0a2'
+    preferences["providers_acoustid__api_key"] = "test"
+    track_file = factories["music.TrackFile"](acoustid_track_id=None)
+    id = "e475bf79-c1ce-4441-bed7-1e33f226c0a2"
     payload = {
-        'results': [
-            {'id': id,
-             'recordings': [
-                {'artists': [
-                    {'id': '9c6bddde-6228-4d9f-ad0d-03f6fcb19e13',
-                     'name': 'Binärpilot'}],
-                 'duration': 268,
-                 'id': 'f269d497-1cc0-4ae4-a0c4-157ec7d73fcb',
-                 'title': 'Bend'}],
-            'score': 0.860825}],
-        'status': 'ok'
+        "results": [
+            {
+                "id": id,
+                "recordings": [
+                    {
+                        "artists": [
+                            {
+                                "id": "9c6bddde-6228-4d9f-ad0d-03f6fcb19e13",
+                                "name": "Binärpilot",
+                            }
+                        ],
+                        "duration": 268,
+                        "id": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb",
+                        "title": "Bend",
+                    }
+                ],
+                "score": 0.860825,
+            }
+        ],
+        "status": "ok",
     }
-    m = mocker.patch('acoustid.match', return_value=payload)
+    m = mocker.patch("acoustid.match", return_value=payload)
     r = tasks.set_acoustid_on_track_file(track_file_id=track_file.pk)
     track_file.refresh_from_db()
 
     assert str(track_file.acoustid_track_id) == id
     assert r == id
-    m.assert_called_once_with('test', track_file.audio_file.path, parse=False)
+    m.assert_called_once_with("test", track_file.audio_file.path, parse=False)
 
 
 def test_set_acoustid_on_track_file_required_high_score(factories, mocker):
-    track_file = factories['music.TrackFile'](acoustid_track_id=None)
-    id = 'e475bf79-c1ce-4441-bed7-1e33f226c0a2'
-    payload = {
-        'results': [{'score': 0.79}],
-        'status': 'ok'
-    }
-    m = mocker.patch('acoustid.match', return_value=payload)
+    track_file = factories["music.TrackFile"](acoustid_track_id=None)
+    id = "e475bf79-c1ce-4441-bed7-1e33f226c0a2"
+    payload = {"results": [{"score": 0.79}], "status": "ok"}
+    m = mocker.patch("acoustid.match", return_value=payload)
     r = tasks.set_acoustid_on_track_file(track_file_id=track_file.pk)
     track_file.refresh_from_db()
 
@@ -48,110 +54,111 @@ def test_set_acoustid_on_track_file_required_high_score(factories, mocker):
 
 
 def test_import_batch_run(factories, mocker):
-    job = factories['music.ImportJob']()
-    mocked_job_run = mocker.patch(
-        'funkwhale_api.music.tasks.import_job_run.delay')
+    job = factories["music.ImportJob"]()
+    mocked_job_run = mocker.patch("funkwhale_api.music.tasks.import_job_run.delay")
     tasks.import_batch_run(import_batch_id=job.batch.pk)
 
     mocked_job_run.assert_called_once_with(import_job_id=job.pk)
 
 
-@pytest.mark.skip('Acoustid is disabled')
+@pytest.mark.skip("Acoustid is disabled")
 def test_import_job_can_run_with_file_and_acoustid(
-        artists, albums, tracks, preferences, factories, mocker):
-    preferences['providers_acoustid__api_key'] = 'test'
-    path = os.path.join(DATA_DIR, 'test.ogg')
-    mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
+    artists, albums, tracks, preferences, factories, mocker
+):
+    preferences["providers_acoustid__api_key"] = "test"
+    path = os.path.join(DATA_DIR, "test.ogg")
+    mbid = "9968a9d6-8d92-4051-8f76-674e157b6eed"
     acoustid_payload = {
-        'results': [
-            {'id': 'e475bf79-c1ce-4441-bed7-1e33f226c0a2',
-             'recordings': [
-                {
-                 'duration': 268,
-                 'id': mbid}],
-            'score': 0.860825}],
-        'status': 'ok'
+        "results": [
+            {
+                "id": "e475bf79-c1ce-4441-bed7-1e33f226c0a2",
+                "recordings": [{"duration": 268, "id": mbid}],
+                "score": 0.860825,
+            }
+        ],
+        "status": "ok",
     }
     mocker.patch(
-        'funkwhale_api.music.utils.get_audio_file_data',
-        return_value={'bitrate': 42, 'length': 43})
+        "funkwhale_api.music.utils.get_audio_file_data",
+        return_value={"bitrate": 42, "length": 43},
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.artists.get',
-        return_value=artists['get']['adhesive_wombat'])
+        "funkwhale_api.musicbrainz.api.artists.get",
+        return_value=artists["get"]["adhesive_wombat"],
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.releases.get',
-        return_value=albums['get']['marsupial'])
+        "funkwhale_api.musicbrainz.api.releases.get",
+        return_value=albums["get"]["marsupial"],
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.recordings.search',
-        return_value=tracks['search']['8bitadventures'])
-    mocker.patch('acoustid.match', return_value=acoustid_payload)
+        "funkwhale_api.musicbrainz.api.recordings.search",
+        return_value=tracks["search"]["8bitadventures"],
+    )
+    mocker.patch("acoustid.match", return_value=acoustid_payload)
 
-    job = factories['music.FileImportJob'](audio_file__path=path)
+    job = factories["music.FileImportJob"](audio_file__path=path)
     f = job.audio_file
     tasks.import_job_run(import_job_id=job.pk)
     job.refresh_from_db()
 
     track_file = job.track_file
 
-    with open(path, 'rb') as f:
+    with open(path, "rb") as f:
         assert track_file.audio_file.read() == f.read()
     assert track_file.bitrate == 42
     assert track_file.duration == 43
     assert track_file.size == os.path.getsize(path)
     # audio file is deleted from import job once persisted to audio file
     assert not job.audio_file
-    assert job.status == 'finished'
-    assert job.source == 'file://'
+    assert job.status == "finished"
+    assert job.source == "file://"
 
 
 def test_run_import_skipping_accoustid(factories, mocker):
-    m = mocker.patch('funkwhale_api.music.tasks._do_import')
-    path = os.path.join(DATA_DIR, 'test.ogg')
-    job = factories['music.FileImportJob'](audio_file__path=path)
+    m = mocker.patch("funkwhale_api.music.tasks._do_import")
+    path = os.path.join(DATA_DIR, "test.ogg")
+    job = factories["music.FileImportJob"](audio_file__path=path)
     tasks.import_job_run(import_job_id=job.pk, use_acoustid=False)
     m.assert_called_once_with(job, False, use_acoustid=False)
 
 
 def test__do_import_skipping_accoustid(factories, mocker):
-    t = factories['music.Track']()
+    t = factories["music.Track"]()
     m = mocker.patch(
-        'funkwhale_api.providers.audiofile.tasks.import_track_data_from_path',
-        return_value=t)
-    path = os.path.join(DATA_DIR, 'test.ogg')
-    job = factories['music.FileImportJob'](
-        mbid=None,
-        audio_file__path=path)
+        "funkwhale_api.providers.audiofile.tasks.import_track_data_from_path",
+        return_value=t,
+    )
+    path = os.path.join(DATA_DIR, "test.ogg")
+    job = factories["music.FileImportJob"](mbid=None, audio_file__path=path)
     p = job.audio_file.path
     tasks._do_import(job, replace=False, use_acoustid=False)
     m.assert_called_once_with(p)
 
 
-def test__do_import_skipping_accoustid_if_no_key(
-        factories, mocker, preferences):
-    preferences['providers_acoustid__api_key'] = ''
-    t = factories['music.Track']()
+def test__do_import_skipping_accoustid_if_no_key(factories, mocker, preferences):
+    preferences["providers_acoustid__api_key"] = ""
+    t = factories["music.Track"]()
     m = mocker.patch(
-        'funkwhale_api.providers.audiofile.tasks.import_track_data_from_path',
-        return_value=t)
-    path = os.path.join(DATA_DIR, 'test.ogg')
-    job = factories['music.FileImportJob'](
-        mbid=None,
-        audio_file__path=path)
+        "funkwhale_api.providers.audiofile.tasks.import_track_data_from_path",
+        return_value=t,
+    )
+    path = os.path.join(DATA_DIR, "test.ogg")
+    job = factories["music.FileImportJob"](mbid=None, audio_file__path=path)
     p = job.audio_file.path
     tasks._do_import(job, replace=False, use_acoustid=False)
     m.assert_called_once_with(p)
 
 
-def test_import_job_skip_if_already_exists(
-        artists, albums, tracks, factories, mocker):
-    path = os.path.join(DATA_DIR, 'test.ogg')
-    mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
-    track_file = factories['music.TrackFile'](track__mbid=mbid)
+def test_import_job_skip_if_already_exists(artists, albums, tracks, factories, mocker):
+    path = os.path.join(DATA_DIR, "test.ogg")
+    mbid = "9968a9d6-8d92-4051-8f76-674e157b6eed"
+    track_file = factories["music.TrackFile"](track__mbid=mbid)
     mocker.patch(
-        'funkwhale_api.providers.audiofile.tasks.import_track_data_from_path',
-        return_value=track_file.track)
+        "funkwhale_api.providers.audiofile.tasks.import_track_data_from_path",
+        return_value=track_file.track,
+    )
 
-    job = factories['music.FileImportJob'](audio_file__path=path)
+    job = factories["music.FileImportJob"](audio_file__path=path)
     f = job.audio_file
     tasks.import_job_run(import_job_id=job.pk)
     job.refresh_from_db()
@@ -159,23 +166,20 @@ def test_import_job_skip_if_already_exists(
     assert job.track_file is None
     # audio file is deleted from import job once persisted to audio file
     assert not job.audio_file
-    assert job.status == 'skipped'
+    assert job.status == "skipped"
 
 
 def test_import_job_can_be_errored(factories, mocker, preferences):
-    path = os.path.join(DATA_DIR, 'test.ogg')
-    mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
-    track_file = factories['music.TrackFile'](track__mbid=mbid)
+    path = os.path.join(DATA_DIR, "test.ogg")
+    mbid = "9968a9d6-8d92-4051-8f76-674e157b6eed"
+    track_file = factories["music.TrackFile"](track__mbid=mbid)
 
     class MyException(Exception):
         pass
 
-    mocker.patch(
-        'funkwhale_api.music.tasks._do_import',
-        side_effect=MyException())
+    mocker.patch("funkwhale_api.music.tasks._do_import", side_effect=MyException())
 
-    job = factories['music.FileImportJob'](
-        audio_file__path=path, track_file=None)
+    job = factories["music.FileImportJob"](audio_file__path=path, track_file=None)
 
     with pytest.raises(MyException):
         tasks.import_job_run(import_job_id=job.pk)
@@ -183,23 +187,22 @@ def test_import_job_can_be_errored(factories, mocker, preferences):
     job.refresh_from_db()
 
     assert job.track_file is None
-    assert job.status == 'errored'
+    assert job.status == "errored"
 
 
 def test__do_import_calls_update_album_cover_if_no_cover(factories, mocker):
-    path = os.path.join(DATA_DIR, 'test.ogg')
-    album = factories['music.Album'](cover='')
-    track = factories['music.Track'](album=album)
+    path = os.path.join(DATA_DIR, "test.ogg")
+    album = factories["music.Album"](cover="")
+    track = factories["music.Track"](album=album)
 
     mocker.patch(
-        'funkwhale_api.providers.audiofile.tasks.import_track_data_from_path',
-        return_value=track)
+        "funkwhale_api.providers.audiofile.tasks.import_track_data_from_path",
+        return_value=track,
+    )
 
-    mocked_update = mocker.patch(
-        'funkwhale_api.music.tasks.update_album_cover')
+    mocked_update = mocker.patch("funkwhale_api.music.tasks.update_album_cover")
 
-    job = factories['music.FileImportJob'](
-        audio_file__path=path, track_file=None)
+    job = factories["music.FileImportJob"](audio_file__path=path, track_file=None)
 
     tasks.import_job_run(import_job_id=job.pk)
 
@@ -207,50 +210,43 @@ def test__do_import_calls_update_album_cover_if_no_cover(factories, mocker):
 
 
 def test_update_album_cover_mbid(factories, mocker):
-    album = factories['music.Album'](cover='')
+    album = factories["music.Album"](cover="")
 
-    mocked_get = mocker.patch('funkwhale_api.music.models.Album.get_image')
+    mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
     tasks.update_album_cover(album=album, track_file=None)
 
     mocked_get.assert_called_once_with()
 
 
 def test_update_album_cover_file_data(factories, mocker):
-    path = os.path.join(DATA_DIR, 'test.mp3')
-    album = factories['music.Album'](cover='', mbid=None)
-    tf = factories['music.TrackFile'](track__album=album)
+    path = os.path.join(DATA_DIR, "test.mp3")
+    album = factories["music.Album"](cover="", mbid=None)
+    tf = factories["music.TrackFile"](track__album=album)
 
-    mocked_get = mocker.patch('funkwhale_api.music.models.Album.get_image')
+    mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
     mocker.patch(
-        'funkwhale_api.music.metadata.Metadata.get_picture',
-        return_value={'hello': 'world'})
+        "funkwhale_api.music.metadata.Metadata.get_picture",
+        return_value={"hello": "world"},
+    )
     tasks.update_album_cover(album=album, track_file=tf)
     md = data = tf.get_metadata()
-    mocked_get.assert_called_once_with(
-        data={'hello': 'world'})
-
-
-@pytest.mark.parametrize('ext,mimetype', [
-    ('jpg', 'image/jpeg'),
-    ('png', 'image/png'),
-])
-def test_update_album_cover_file_cover_separate_file(
-        ext, mimetype, factories, mocker):
-    mocker.patch('funkwhale_api.music.tasks.IMAGE_TYPES', [(ext, mimetype)])
-    path = os.path.join(DATA_DIR, 'test.mp3')
-    image_path = os.path.join(DATA_DIR, 'cover.{}'.format(ext))
-    with open(image_path, 'rb') as f:
+    mocked_get.assert_called_once_with(data={"hello": "world"})
+
+
+@pytest.mark.parametrize("ext,mimetype", [("jpg", "image/jpeg"), ("png", "image/png")])
+def test_update_album_cover_file_cover_separate_file(ext, mimetype, factories, mocker):
+    mocker.patch("funkwhale_api.music.tasks.IMAGE_TYPES", [(ext, mimetype)])
+    path = os.path.join(DATA_DIR, "test.mp3")
+    image_path = os.path.join(DATA_DIR, "cover.{}".format(ext))
+    with open(image_path, "rb") as f:
         image_content = f.read()
-    album = factories['music.Album'](cover='', mbid=None)
-    tf = factories['music.TrackFile'](
-        track__album=album,
-        source='file://' + image_path)
+    album = factories["music.Album"](cover="", mbid=None)
+    tf = factories["music.TrackFile"](track__album=album, source="file://" + image_path)
 
-    mocked_get = mocker.patch('funkwhale_api.music.models.Album.get_image')
-    mocker.patch(
-        'funkwhale_api.music.metadata.Metadata.get_picture',
-        return_value=None)
+    mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
+    mocker.patch("funkwhale_api.music.metadata.Metadata.get_picture", return_value=None)
     tasks.update_album_cover(album=album, track_file=tf)
     md = data = tf.get_metadata()
     mocked_get.assert_called_once_with(
-        data={'mimetype': mimetype, 'content': image_content})
+        data={"mimetype": mimetype, "content": image_content}
+    )
diff --git a/api/tests/music/test_utils.py b/api/tests/music/test_utils.py
index 7b967dbbcc..276a450d4c 100644
--- a/api/tests/music/test_utils.py
+++ b/api/tests/music/test_utils.py
@@ -7,35 +7,31 @@ DATA_DIR = os.path.dirname(os.path.abspath(__file__))
 
 
 def test_guess_mimetype_try_using_extension(factories, mocker):
-    mocker.patch(
-        'magic.from_buffer', return_value='audio/mpeg')
-    f = factories['music.TrackFile'].build(
-        audio_file__filename='test.ogg')
+    mocker.patch("magic.from_buffer", return_value="audio/mpeg")
+    f = factories["music.TrackFile"].build(audio_file__filename="test.ogg")
 
-    assert utils.guess_mimetype(f.audio_file) == 'audio/mpeg'
+    assert utils.guess_mimetype(f.audio_file) == "audio/mpeg"
 
 
-@pytest.mark.parametrize('wrong', [
-    'application/octet-stream',
-    'application/x-empty',
-])
+@pytest.mark.parametrize("wrong", ["application/octet-stream", "application/x-empty"])
 def test_guess_mimetype_try_using_extension_if_fail(wrong, factories, mocker):
-    mocker.patch(
-        'magic.from_buffer', return_value=wrong)
-    f = factories['music.TrackFile'].build(
-        audio_file__filename='test.mp3')
+    mocker.patch("magic.from_buffer", return_value=wrong)
+    f = factories["music.TrackFile"].build(audio_file__filename="test.mp3")
 
-    assert utils.guess_mimetype(f.audio_file) == 'audio/mpeg'
+    assert utils.guess_mimetype(f.audio_file) == "audio/mpeg"
 
 
-@pytest.mark.parametrize('name, expected', [
-    ('sample.flac', {'bitrate': 1608000, 'length': 0.001}),
-    ('test.mp3', {'bitrate': 8000, 'length': 267.70285714285717}),
-    ('test.ogg', {'bitrate': 128000, 'length': 229.18304166666667}),
-])
+@pytest.mark.parametrize(
+    "name, expected",
+    [
+        ("sample.flac", {"bitrate": 1608000, "length": 0.001}),
+        ("test.mp3", {"bitrate": 8000, "length": 267.70285714285717}),
+        ("test.ogg", {"bitrate": 128000, "length": 229.18304166666667}),
+    ],
+)
 def test_get_audio_file_data(name, expected):
     path = os.path.join(DATA_DIR, name)
-    with open(path, 'rb') as f:
+    with open(path, "rb") as f:
         result = utils.get_audio_file_data(f)
 
     assert result == expected
diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py
index 91fef13f2f..139a33cf5d 100644
--- a/api/tests/music/test_views.py
+++ b/api/tests/music/test_views.py
@@ -9,28 +9,27 @@ from funkwhale_api.music import views
 from funkwhale_api.federation import actors
 
 
-@pytest.mark.parametrize('view,permissions,operator', [
-    (views.ImportBatchViewSet, ['library', 'upload'], 'or'),
-    (views.ImportJobViewSet, ['library', 'upload'], 'or'),
-])
+@pytest.mark.parametrize(
+    "view,permissions,operator",
+    [
+        (views.ImportBatchViewSet, ["library", "upload"], "or"),
+        (views.ImportJobViewSet, ["library", "upload"], "or"),
+    ],
+)
 def test_permissions(assert_user_permission, view, permissions, operator):
     assert_user_permission(view, permissions, operator)
 
 
 def test_artist_list_serializer(api_request, factories, logged_in_api_client):
-    track = factories['music.Track']()
+    track = factories["music.Track"]()
     artist = track.artist
-    request = api_request.get('/')
+    request = api_request.get("/")
     qs = artist.__class__.objects.with_albums()
     serializer = serializers.ArtistWithAlbumsSerializer(
-        qs, many=True, context={'request': request})
-    expected = {
-        'count': 1,
-        'next': None,
-        'previous': None,
-        'results': serializer.data
-    }
-    url = reverse('api:v1:artists-list')
+        qs, many=True, context={"request": request}
+    )
+    expected = {"count": 1, "next": None, "previous": None, "results": serializer.data}
+    url = reverse("api:v1:artists-list")
     response = logged_in_api_client.get(url)
 
     assert response.status_code == 200
@@ -38,19 +37,15 @@ def test_artist_list_serializer(api_request, factories, logged_in_api_client):
 
 
 def test_album_list_serializer(api_request, factories, logged_in_api_client):
-    track = factories['music.Track']()
+    track = factories["music.Track"]()
     album = track.album
-    request = api_request.get('/')
+    request = api_request.get("/")
     qs = album.__class__.objects.all()
     serializer = serializers.AlbumSerializer(
-        qs, many=True, context={'request': request})
-    expected = {
-        'count': 1,
-        'next': None,
-        'previous': None,
-        'results': serializer.data
-    }
-    url = reverse('api:v1:albums-list')
+        qs, many=True, context={"request": request}
+    )
+    expected = {"count": 1, "next": None, "previous": None, "results": serializer.data}
+    url = reverse("api:v1:albums-list")
     response = logged_in_api_client.get(url)
 
     assert response.status_code == 200
@@ -58,38 +53,30 @@ def test_album_list_serializer(api_request, factories, logged_in_api_client):
 
 
 def test_track_list_serializer(api_request, factories, logged_in_api_client):
-    track = factories['music.Track']()
-    request = api_request.get('/')
+    track = factories["music.Track"]()
+    request = api_request.get("/")
     qs = track.__class__.objects.all()
     serializer = serializers.TrackSerializer(
-        qs, many=True, context={'request': request})
-    expected = {
-        'count': 1,
-        'next': None,
-        'previous': None,
-        'results': serializer.data
-    }
-    url = reverse('api:v1:tracks-list')
+        qs, many=True, context={"request": request}
+    )
+    expected = {"count": 1, "next": None, "previous": None, "results": serializer.data}
+    url = reverse("api:v1:tracks-list")
     response = logged_in_api_client.get(url)
 
     assert response.status_code == 200
     assert response.data == expected
 
 
-@pytest.mark.parametrize('param,expected', [
-    ('true', 'full'),
-    ('false', 'empty'),
-])
-def test_artist_view_filter_listenable(
-        param, expected, factories, api_request):
+@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,
+        "empty": factories["music.Artist"](),
+        "full": factories["music.TrackFile"]().track.artist,
     }
 
-    request = api_request.get('/', {'listenable': param})
+    request = api_request.get("/", {"listenable": param})
     view = views.ArtistViewSet()
-    view.action_map = {'get': 'list'}
+    view.action_map = {"get": "list"}
     expected = [artists[expected]]
     view.request = view.initialize_request(request)
     queryset = view.filter_queryset(view.get_queryset())
@@ -97,20 +84,16 @@ def test_artist_view_filter_listenable(
     assert list(queryset) == expected
 
 
-@pytest.mark.parametrize('param,expected', [
-    ('true', 'full'),
-    ('false', 'empty'),
-])
-def test_album_view_filter_listenable(
-        param, expected, factories, api_request):
+@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,
+        "empty": factories["music.Album"](),
+        "full": factories["music.TrackFile"]().track.album,
     }
 
-    request = api_request.get('/', {'listenable': param})
+    request = api_request.get("/", {"listenable": param})
     view = views.AlbumViewSet()
-    view.action_map = {'get': 'list'}
+    view.action_map = {"get": "list"}
     expected = [artists[expected]]
     view.request = view.initialize_request(request)
     queryset = view.filter_queryset(view.get_queryset())
@@ -119,58 +102,53 @@ def test_album_view_filter_listenable(
 
 
 def test_can_serve_track_file_as_remote_library(
-        factories, authenticated_actor, api_client, settings, preferences):
-    preferences['common__api_authentication_required'] = True
-    library_actor = actors.SYSTEM_ACTORS['library'].get_actor_instance()
-    follow = factories['federation.Follow'](
-        approved=True,
-        actor=authenticated_actor,
-        target=library_actor)
-
-    track_file = factories['music.TrackFile']()
+    factories, authenticated_actor, api_client, settings, preferences
+):
+    preferences["common__api_authentication_required"] = True
+    library_actor = actors.SYSTEM_ACTORS["library"].get_actor_instance()
+    follow = factories["federation.Follow"](
+        approved=True, actor=authenticated_actor, target=library_actor
+    )
+
+    track_file = factories["music.TrackFile"]()
     response = api_client.get(track_file.path)
 
     assert response.status_code == 200
-    assert response['X-Accel-Redirect'] == "{}{}".format(
-        settings.PROTECT_FILES_PATH,
-        track_file.audio_file.url)
+    assert response["X-Accel-Redirect"] == "{}{}".format(
+        settings.PROTECT_FILES_PATH, track_file.audio_file.url
+    )
 
 
 def test_can_serve_track_file_as_remote_library_deny_not_following(
-        factories, authenticated_actor, settings, api_client, preferences):
-    preferences['common__api_authentication_required'] = True
-    track_file = factories['music.TrackFile']()
+    factories, authenticated_actor, settings, api_client, preferences
+):
+    preferences["common__api_authentication_required"] = True
+    track_file = factories["music.TrackFile"]()
     response = api_client.get(track_file.path)
 
     assert response.status_code == 403
 
 
-@pytest.mark.parametrize('proxy,serve_path,expected', [
-    ('apache2', '/host/music', '/host/music/hello/world.mp3'),
-    ('apache2', '/app/music', '/app/music/hello/world.mp3'),
-    ('nginx', '/host/music', '/_protected/music/hello/world.mp3'),
-    ('nginx', '/app/music', '/_protected/music/hello/world.mp3'),
-])
+@pytest.mark.parametrize(
+    "proxy,serve_path,expected",
+    [
+        ("apache2", "/host/music", "/host/music/hello/world.mp3"),
+        ("apache2", "/app/music", "/app/music/hello/world.mp3"),
+        ("nginx", "/host/music", "/_protected/music/hello/world.mp3"),
+        ("nginx", "/app/music", "/_protected/music/hello/world.mp3"),
+    ],
+)
 def test_serve_file_in_place(
-        proxy,
-        serve_path,
-        expected,
-        factories,
-        api_client,
-        preferences,
-        settings):
-    headers = {
-        'apache2': 'X-Sendfile',
-        'nginx': 'X-Accel-Redirect',
-    }
-    preferences['common__api_authentication_required'] = False
-    settings.PROTECT_FILE_PATH = '/_protected/music'
+    proxy, serve_path, expected, factories, api_client, preferences, settings
+):
+    headers = {"apache2": "X-Sendfile", "nginx": "X-Accel-Redirect"}
+    preferences["common__api_authentication_required"] = False
+    settings.PROTECT_FILE_PATH = "/_protected/music"
     settings.REVERSE_PROXY_TYPE = proxy
-    settings.MUSIC_DIRECTORY_PATH = '/app/music'
+    settings.MUSIC_DIRECTORY_PATH = "/app/music"
     settings.MUSIC_DIRECTORY_SERVE_PATH = serve_path
-    tf = factories['music.TrackFile'](
-        in_place=True,
-        source='file:///app/music/hello/world.mp3'
+    tf = factories["music.TrackFile"](
+        in_place=True, source="file:///app/music/hello/world.mp3"
     )
     response = api_client.get(tf.path)
 
@@ -178,86 +156,76 @@ def test_serve_file_in_place(
     assert response[headers[proxy]] == expected
 
 
-@pytest.mark.parametrize('proxy,serve_path,expected', [
-    ('apache2', '/host/music', '/host/music/hello/worldéà.mp3'),
-    ('apache2', '/app/music', '/app/music/hello/worldéà.mp3'),
-    ('nginx', '/host/music', '/_protected/music/hello/worldéà.mp3'),
-    ('nginx', '/app/music', '/_protected/music/hello/worldéà.mp3'),
-])
+@pytest.mark.parametrize(
+    "proxy,serve_path,expected",
+    [
+        ("apache2", "/host/music", "/host/music/hello/worldéà.mp3"),
+        ("apache2", "/app/music", "/app/music/hello/worldéà.mp3"),
+        ("nginx", "/host/music", "/_protected/music/hello/worldéà.mp3"),
+        ("nginx", "/app/music", "/_protected/music/hello/worldéà.mp3"),
+    ],
+)
 def test_serve_file_in_place_utf8(
-        proxy,
-        serve_path,
-        expected,
-        factories,
-        api_client,
-        settings,
-        preferences):
-    preferences['common__api_authentication_required'] = False
-    settings.PROTECT_FILE_PATH = '/_protected/music'
+    proxy, serve_path, expected, factories, api_client, settings, preferences
+):
+    preferences["common__api_authentication_required"] = False
+    settings.PROTECT_FILE_PATH = "/_protected/music"
     settings.REVERSE_PROXY_TYPE = proxy
-    settings.MUSIC_DIRECTORY_PATH = '/app/music'
+    settings.MUSIC_DIRECTORY_PATH = "/app/music"
     settings.MUSIC_DIRECTORY_SERVE_PATH = serve_path
-    path = views.get_file_path('/app/music/hello/worldéà.mp3')
+    path = views.get_file_path("/app/music/hello/worldéà.mp3")
 
-    assert path == expected.encode('utf-8')
+    assert path == expected.encode("utf-8")
 
 
-@pytest.mark.parametrize('proxy,serve_path,expected', [
-    ('apache2', '/host/music', '/host/media/tracks/hello/world.mp3'),
-    # apache with container not supported yet
-    # ('apache2', '/app/music', '/app/music/tracks/hello/world.mp3'),
-    ('nginx', '/host/music', '/_protected/media/tracks/hello/world.mp3'),
-    ('nginx', '/app/music', '/_protected/media/tracks/hello/world.mp3'),
-])
+@pytest.mark.parametrize(
+    "proxy,serve_path,expected",
+    [
+        ("apache2", "/host/music", "/host/media/tracks/hello/world.mp3"),
+        # apache with container not supported yet
+        # ('apache2', '/app/music', '/app/music/tracks/hello/world.mp3'),
+        ("nginx", "/host/music", "/_protected/media/tracks/hello/world.mp3"),
+        ("nginx", "/app/music", "/_protected/media/tracks/hello/world.mp3"),
+    ],
+)
 def test_serve_file_media(
-        proxy,
-        serve_path,
-        expected,
-        factories,
-        api_client,
-        settings,
-        preferences):
-    headers = {
-        'apache2': 'X-Sendfile',
-        'nginx': 'X-Accel-Redirect',
-    }
-    preferences['common__api_authentication_required'] = False
-    settings.MEDIA_ROOT = '/host/media'
-    settings.PROTECT_FILE_PATH = '/_protected/music'
+    proxy, serve_path, expected, factories, api_client, settings, preferences
+):
+    headers = {"apache2": "X-Sendfile", "nginx": "X-Accel-Redirect"}
+    preferences["common__api_authentication_required"] = False
+    settings.MEDIA_ROOT = "/host/media"
+    settings.PROTECT_FILE_PATH = "/_protected/music"
     settings.REVERSE_PROXY_TYPE = proxy
-    settings.MUSIC_DIRECTORY_PATH = '/app/music'
+    settings.MUSIC_DIRECTORY_PATH = "/app/music"
     settings.MUSIC_DIRECTORY_SERVE_PATH = serve_path
 
-    tf = factories['music.TrackFile']()
-    tf.__class__.objects.filter(pk=tf.pk).update(
-        audio_file='tracks/hello/world.mp3')
+    tf = factories["music.TrackFile"]()
+    tf.__class__.objects.filter(pk=tf.pk).update(audio_file="tracks/hello/world.mp3")
     response = api_client.get(tf.path)
 
     assert response.status_code == 200
     assert response[headers[proxy]] == expected
 
 
-def test_can_proxy_remote_track(
-        factories, settings, api_client, r_mock, preferences):
-    preferences['common__api_authentication_required'] = False
-    track_file = factories['music.TrackFile'](federation=True)
+def test_can_proxy_remote_track(factories, settings, api_client, r_mock, preferences):
+    preferences["common__api_authentication_required"] = False
+    track_file = factories["music.TrackFile"](federation=True)
 
-    r_mock.get(track_file.library_track.audio_url, body=io.BytesIO(b'test'))
+    r_mock.get(track_file.library_track.audio_url, body=io.BytesIO(b"test"))
     response = api_client.get(track_file.path)
 
     library_track = track_file.library_track
     library_track.refresh_from_db()
     assert response.status_code == 200
-    assert response['X-Accel-Redirect'] == "{}{}".format(
-        settings.PROTECT_FILES_PATH,
-        library_track.audio_file.url)
-    assert library_track.audio_file.read() == b'test'
+    assert response["X-Accel-Redirect"] == "{}{}".format(
+        settings.PROTECT_FILES_PATH, library_track.audio_file.url
+    )
+    assert library_track.audio_file.read() == b"test"
 
 
-def test_serve_updates_access_date(
-        factories, settings, api_client, preferences):
-    preferences['common__api_authentication_required'] = False
-    track_file = factories['music.TrackFile']()
+def test_serve_updates_access_date(factories, settings, api_client, preferences):
+    preferences["common__api_authentication_required"] = False
+    track_file = factories["music.TrackFile"]()
     now = timezone.now()
     assert track_file.accessed_date is None
 
@@ -269,128 +237,118 @@ def test_serve_updates_access_date(
 
 
 def test_can_list_import_jobs(factories, superuser_api_client):
-    job = factories['music.ImportJob']()
-    url = reverse('api:v1:import-jobs-list')
+    job = factories["music.ImportJob"]()
+    url = reverse("api:v1:import-jobs-list")
     response = superuser_api_client.get(url)
 
     assert response.status_code == 200
-    assert response.data['results'][0]['id'] == job.pk
+    assert response.data["results"][0]["id"] == job.pk
 
 
 def test_import_job_stats(factories, superuser_api_client):
-    job1 = factories['music.ImportJob'](status='pending')
-    job2 = factories['music.ImportJob'](status='errored')
+    job1 = factories["music.ImportJob"](status="pending")
+    job2 = factories["music.ImportJob"](status="errored")
 
-    url = reverse('api:v1:import-jobs-stats')
+    url = reverse("api:v1:import-jobs-stats")
     response = superuser_api_client.get(url)
-    expected = {
-        'errored': 1,
-        'pending': 1,
-        'finished': 0,
-        'skipped': 0,
-        'count': 2,
-    }
+    expected = {"errored": 1, "pending": 1, "finished": 0, "skipped": 0, "count": 2}
     assert response.status_code == 200
     assert response.data == expected
 
 
 def test_import_job_stats_filter(factories, superuser_api_client):
-    job1 = factories['music.ImportJob'](status='pending')
-    job2 = factories['music.ImportJob'](status='errored')
-
-    url = reverse('api:v1:import-jobs-stats')
-    response = superuser_api_client.get(url, {'batch': job1.batch.pk})
-    expected = {
-        'errored': 0,
-        'pending': 1,
-        'finished': 0,
-        'skipped': 0,
-        'count': 1,
-    }
+    job1 = factories["music.ImportJob"](status="pending")
+    job2 = factories["music.ImportJob"](status="errored")
+
+    url = reverse("api:v1:import-jobs-stats")
+    response = superuser_api_client.get(url, {"batch": job1.batch.pk})
+    expected = {"errored": 0, "pending": 1, "finished": 0, "skipped": 0, "count": 1}
     assert response.status_code == 200
     assert response.data == expected
 
 
 def test_import_job_run_via_api(factories, superuser_api_client, mocker):
-    run = mocker.patch('funkwhale_api.music.tasks.import_job_run.delay')
-    job1 = factories['music.ImportJob'](status='errored')
-    job2 = factories['music.ImportJob'](status='pending')
+    run = mocker.patch("funkwhale_api.music.tasks.import_job_run.delay")
+    job1 = factories["music.ImportJob"](status="errored")
+    job2 = factories["music.ImportJob"](status="pending")
 
-    url = reverse('api:v1:import-jobs-run')
-    response = superuser_api_client.post(url, {'jobs': [job2.pk, job1.pk]})
+    url = reverse("api:v1:import-jobs-run")
+    response = superuser_api_client.post(url, {"jobs": [job2.pk, job1.pk]})
 
     job1.refresh_from_db()
     job2.refresh_from_db()
     assert response.status_code == 200
-    assert response.data == {'jobs': [job1.pk, job2.pk]}
-    assert job1.status == 'pending'
-    assert job2.status == 'pending'
+    assert response.data == {"jobs": [job1.pk, job2.pk]}
+    assert job1.status == "pending"
+    assert job2.status == "pending"
 
     run.assert_any_call(import_job_id=job1.pk)
     run.assert_any_call(import_job_id=job2.pk)
 
 
 def test_import_batch_run_via_api(factories, superuser_api_client, mocker):
-    run = mocker.patch('funkwhale_api.music.tasks.import_job_run.delay')
+    run = mocker.patch("funkwhale_api.music.tasks.import_job_run.delay")
 
-    batch = factories['music.ImportBatch']()
-    job1 = factories['music.ImportJob'](batch=batch, status='errored')
-    job2 = factories['music.ImportJob'](batch=batch, status='pending')
+    batch = factories["music.ImportBatch"]()
+    job1 = factories["music.ImportJob"](batch=batch, status="errored")
+    job2 = factories["music.ImportJob"](batch=batch, status="pending")
 
-    url = reverse('api:v1:import-jobs-run')
-    response = superuser_api_client.post(url, {'batches': [batch.pk]})
+    url = reverse("api:v1:import-jobs-run")
+    response = superuser_api_client.post(url, {"batches": [batch.pk]})
 
     job1.refresh_from_db()
     job2.refresh_from_db()
     assert response.status_code == 200
-    assert job1.status == 'pending'
-    assert job2.status == 'pending'
+    assert job1.status == "pending"
+    assert job2.status == "pending"
 
     run.assert_any_call(import_job_id=job1.pk)
     run.assert_any_call(import_job_id=job2.pk)
 
 
-def test_import_batch_and_job_run_via_api(
-        factories, superuser_api_client, mocker):
-    run = mocker.patch('funkwhale_api.music.tasks.import_job_run.delay')
+def test_import_batch_and_job_run_via_api(factories, superuser_api_client, mocker):
+    run = mocker.patch("funkwhale_api.music.tasks.import_job_run.delay")
 
-    batch = factories['music.ImportBatch']()
-    job1 = factories['music.ImportJob'](batch=batch, status='errored')
-    job2 = factories['music.ImportJob'](status='pending')
+    batch = factories["music.ImportBatch"]()
+    job1 = factories["music.ImportJob"](batch=batch, status="errored")
+    job2 = factories["music.ImportJob"](status="pending")
 
-    url = reverse('api:v1:import-jobs-run')
+    url = reverse("api:v1:import-jobs-run")
     response = superuser_api_client.post(
-        url, {'batches': [batch.pk], 'jobs': [job2.pk]})
+        url, {"batches": [batch.pk], "jobs": [job2.pk]}
+    )
 
     job1.refresh_from_db()
     job2.refresh_from_db()
     assert response.status_code == 200
-    assert job1.status == 'pending'
-    assert job2.status == 'pending'
+    assert job1.status == "pending"
+    assert job2.status == "pending"
 
     run.assert_any_call(import_job_id=job1.pk)
     run.assert_any_call(import_job_id=job2.pk)
 
 
 def test_import_job_viewset_get_queryset_upload_filters_user(
-        factories, logged_in_api_client):
+    factories, logged_in_api_client
+):
     logged_in_api_client.user.permission_upload = True
     logged_in_api_client.user.save()
 
-    job = factories['music.ImportJob']()
-    url = reverse('api:v1:import-jobs-list')
+    job = factories["music.ImportJob"]()
+    url = reverse("api:v1:import-jobs-list")
     response = logged_in_api_client.get(url)
 
-    assert response.data['count'] == 0
+    assert response.data["count"] == 0
 
 
 def test_import_batch_viewset_get_queryset_upload_filters_user(
-        factories, logged_in_api_client):
+    factories, logged_in_api_client
+):
     logged_in_api_client.user.permission_upload = True
     logged_in_api_client.user.save()
 
-    job = factories['music.ImportBatch']()
-    url = reverse('api:v1:import-batches-list')
+    job = factories["music.ImportBatch"]()
+    url = reverse("api:v1:import-batches-list")
     response = logged_in_api_client.get(url)
 
-    assert response.data['count'] == 0
+    assert response.data["count"] == 0
diff --git a/api/tests/music/test_works.py b/api/tests/music/test_works.py
index 13f6447bec..6b2e57fdc9 100644
--- a/api/tests/music/test_works.py
+++ b/api/tests/music/test_works.py
@@ -8,16 +8,16 @@ from funkwhale_api.music import serializers
 
 def test_can_import_work(factories, mocker, works):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.works.get',
-        return_value=works['get']['chop_suey'])
-    recording = factories['music.Track'](
-        mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
-    mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5'
+        "funkwhale_api.musicbrainz.api.works.get",
+        return_value=works["get"]["chop_suey"],
+    )
+    recording = factories["music.Track"](mbid="07ca77cf-f513-4e9c-b190-d7e24bbad448")
+    mbid = "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5"
     work = models.Work.create_from_api(id=mbid)
 
-    assert work.title == 'Chop Suey!'
-    assert work.nature == 'song'
-    assert work.language == 'eng'
+    assert work.title == "Chop Suey!"
+    assert work.nature == "song"
+    assert work.language == "eng"
     assert work.mbid == mbid
 
     # a imported work should also be linked to corresponding recordings
@@ -28,23 +28,25 @@ def test_can_import_work(factories, mocker, works):
 
 def test_can_get_work_from_recording(factories, mocker, works, tracks):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.works.get',
-        return_value=works['get']['chop_suey'])
+        "funkwhale_api.musicbrainz.api.works.get",
+        return_value=works["get"]["chop_suey"],
+    )
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.recordings.get',
-        return_value=tracks['get']['chop_suey'])
-    recording = factories['music.Track'](
-        work=None,
-        mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
-    mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5'
+        "funkwhale_api.musicbrainz.api.recordings.get",
+        return_value=tracks["get"]["chop_suey"],
+    )
+    recording = factories["music.Track"](
+        work=None, mbid="07ca77cf-f513-4e9c-b190-d7e24bbad448"
+    )
+    mbid = "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5"
 
     assert recording.work == None
 
     work = recording.get_work()
 
-    assert work.title == 'Chop Suey!'
-    assert work.nature == 'song'
-    assert work.language == 'eng'
+    assert work.title == "Chop Suey!"
+    assert work.nature == "song"
+    assert work.language == "eng"
     assert work.mbid == mbid
 
     recording.refresh_from_db()
@@ -53,11 +55,12 @@ def test_can_get_work_from_recording(factories, mocker, works, tracks):
 
 def test_works_import_lyrics_if_any(db, mocker, works):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.works.get',
-        return_value=works['get']['chop_suey'])
-    mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5'
+        "funkwhale_api.musicbrainz.api.works.get",
+        return_value=works["get"]["chop_suey"],
+    )
+    mbid = "e2ecabc4-1b9d-30b2-8f30-3596ec423dc5"
     work = models.Work.create_from_api(id=mbid)
 
-    lyrics = models.Lyrics.objects.latest('id')
+    lyrics = models.Lyrics.objects.latest("id")
     assert lyrics.work == work
-    assert lyrics.url == 'http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!'
+    assert lyrics.url == "http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!"
diff --git a/api/tests/musicbrainz/conftest.py b/api/tests/musicbrainz/conftest.py
index 505d6e5537..3e3ebfa481 100644
--- a/api/tests/musicbrainz/conftest.py
+++ b/api/tests/musicbrainz/conftest.py
@@ -1,33 +1,28 @@
 import pytest
 
-_artists = {'search': {}, 'get': {}}
-_artists['search']['lost fingers'] = {
-    'artist-count': 696,
-    'artist-list': [
+_artists = {"search": {}, "get": {}}
+_artists["search"]["lost fingers"] = {
+    "artist-count": 696,
+    "artist-list": [
         {
-            'country': 'CA',
-            'sort-name': 'Lost Fingers, The',
-            'id': 'ac16bbc0-aded-4477-a3c3-1d81693d58c9',
-            'type': 'Group',
-            'life-span': {
-                'ended': 'false',
-                'begin': '2008'
-            },
-            'area': {
-                'sort-name': 'Canada',
-                'id': '71bbafaa-e825-3e15-8ca9-017dcad1748b',
-                'name': 'Canada'
+            "country": "CA",
+            "sort-name": "Lost Fingers, The",
+            "id": "ac16bbc0-aded-4477-a3c3-1d81693d58c9",
+            "type": "Group",
+            "life-span": {"ended": "false", "begin": "2008"},
+            "area": {
+                "sort-name": "Canada",
+                "id": "71bbafaa-e825-3e15-8ca9-017dcad1748b",
+                "name": "Canada",
             },
-            'ext:score': '100',
-            'name': 'The Lost Fingers'
-        },
-    ]
+            "ext:score": "100",
+            "name": "The Lost Fingers",
+        }
+    ],
 }
-_artists['get']['lost fingers'] = {
+_artists["get"]["lost fingers"] = {
     "artist": {
-        "life-span": {
-            "begin": "2008"
-        },
+        "life-span": {"begin": "2008"},
         "type": "Group",
         "id": "ac16bbc0-aded-4477-a3c3-1d81693d58c9",
         "release-group-count": 8,
@@ -38,137 +33,135 @@ _artists['get']['lost fingers'] = {
                 "first-release-date": "2010",
                 "type": "Album",
                 "id": "03d3f1d4-e2b0-40d3-8314-05f1896e93a0",
-                "primary-type": "Album"
+                "primary-type": "Album",
             },
             {
                 "title": "Gitan Kameleon",
                 "first-release-date": "2011-11-11",
                 "type": "Album",
                 "id": "243c0cd2-2492-4f5d-bf37-c7c76bed05b7",
-                "primary-type": "Album"
+                "primary-type": "Album",
             },
             {
                 "title": "Pump Up the Jam \u2013 Do Not Cover, Pt. 3",
                 "first-release-date": "2014-03-17",
                 "type": "Single",
                 "id": "4429befd-ff45-48eb-a8f4-cdf7bf007f3f",
-                "primary-type": "Single"
+                "primary-type": "Single",
             },
             {
                 "title": "La Marquise",
                 "first-release-date": "2012-03-27",
                 "type": "Album",
                 "id": "4dab4b96-0a6b-4507-a31e-2189e3e7bad1",
-                "primary-type": "Album"
+                "primary-type": "Album",
             },
             {
                 "title": "Christmas Caravan",
                 "first-release-date": "2016-11-11",
                 "type": "Album",
                 "id": "ca0a506d-6ba9-47c3-a712-de5ce9ae6b1f",
-                "primary-type": "Album"
+                "primary-type": "Album",
             },
             {
                 "title": "Rendez-vous rose",
                 "first-release-date": "2009-06-16",
                 "type": "Album",
                 "id": "d002f1a8-5890-4188-be58-1caadbbd767f",
-                "primary-type": "Album"
+                "primary-type": "Album",
             },
             {
                 "title": "Wonders of the World",
                 "first-release-date": "2014-05-06",
                 "type": "Album",
                 "id": "eeb644c2-5000-42fb-b959-e5e9cc2901c5",
-                "primary-type": "Album"
+                "primary-type": "Album",
             },
             {
                 "title": "Lost in the 80s",
                 "first-release-date": "2008-05-06",
                 "type": "Album",
                 "id": "f04ed607-11b7-3843-957e-503ecdd485d1",
-                "primary-type": "Album"
-            }
+                "primary-type": "Album",
+            },
         ],
         "area": {
-            "iso-3166-1-code-list": [
-                "CA"
-            ],
+            "iso-3166-1-code-list": ["CA"],
             "name": "Canada",
             "id": "71bbafaa-e825-3e15-8ca9-017dcad1748b",
-            "sort-name": "Canada"
+            "sort-name": "Canada",
         },
         "sort-name": "Lost Fingers, The",
-        "country": "CA"
+        "country": "CA",
     }
 }
 
 
-_release_groups = {'browse': {}}
-_release_groups['browse']["lost fingers"] = {
+_release_groups = {"browse": {}}
+_release_groups["browse"]["lost fingers"] = {
     "release-group-list": [
         {
             "first-release-date": "2010",
             "type": "Album",
             "primary-type": "Album",
             "title": "Gypsy Kameleon",
-            "id": "03d3f1d4-e2b0-40d3-8314-05f1896e93a0"
+            "id": "03d3f1d4-e2b0-40d3-8314-05f1896e93a0",
         },
         {
             "first-release-date": "2011-11-11",
             "type": "Album",
             "primary-type": "Album",
             "title": "Gitan Kameleon",
-            "id": "243c0cd2-2492-4f5d-bf37-c7c76bed05b7"
+            "id": "243c0cd2-2492-4f5d-bf37-c7c76bed05b7",
         },
         {
             "first-release-date": "2014-03-17",
             "type": "Single",
             "primary-type": "Single",
             "title": "Pump Up the Jam \u2013 Do Not Cover, Pt. 3",
-            "id": "4429befd-ff45-48eb-a8f4-cdf7bf007f3f"
+            "id": "4429befd-ff45-48eb-a8f4-cdf7bf007f3f",
         },
         {
             "first-release-date": "2012-03-27",
             "type": "Album",
             "primary-type": "Album",
             "title": "La Marquise",
-            "id": "4dab4b96-0a6b-4507-a31e-2189e3e7bad1"
+            "id": "4dab4b96-0a6b-4507-a31e-2189e3e7bad1",
         },
         {
             "first-release-date": "2016-11-11",
             "type": "Album",
             "primary-type": "Album",
             "title": "Christmas Caravan",
-            "id": "ca0a506d-6ba9-47c3-a712-de5ce9ae6b1f"
+            "id": "ca0a506d-6ba9-47c3-a712-de5ce9ae6b1f",
         },
         {
             "first-release-date": "2009-06-16",
             "type": "Album",
             "primary-type": "Album",
             "title": "Rendez-vous rose",
-            "id": "d002f1a8-5890-4188-be58-1caadbbd767f"
+            "id": "d002f1a8-5890-4188-be58-1caadbbd767f",
         },
         {
             "first-release-date": "2014-05-06",
             "type": "Album",
             "primary-type": "Album",
             "title": "Wonders of the World",
-            "id": "eeb644c2-5000-42fb-b959-e5e9cc2901c5"
+            "id": "eeb644c2-5000-42fb-b959-e5e9cc2901c5",
         },
         {
             "first-release-date": "2008-05-06",
             "type": "Album",
             "primary-type": "Album",
             "title": "Lost in the 80s",
-            "id": "f04ed607-11b7-3843-957e-503ecdd485d1"
-        }
+            "id": "f04ed607-11b7-3843-957e-503ecdd485d1",
+        },
     ],
-    "release-group-count": 8
+    "release-group-count": 8,
 }
 
-_recordings = {'search': {}, 'get': {}}
-_recordings['search']['brontide matador'] = {
+_recordings = {"search": {}, "get": {}}
+_recordings["search"]["brontide matador"] = {
     "recording-count": 1044,
     "recording-list": [
         {
@@ -184,9 +177,9 @@ _recordings['search']['brontide matador'] = {
                                 "name": "United Kingdom",
                                 "sort-name": "United Kingdom",
                                 "id": "8a754a16-0027-3a29-b6d7-2b40ea0481ed",
-                                "iso-3166-1-code-list": ["GB"]
+                                "iso-3166-1-code-list": ["GB"],
                             },
-                            "date": "2011-05-30"
+                            "date": "2011-05-30",
                         }
                     ],
                     "country": "GB",
@@ -196,7 +189,7 @@ _recordings['search']['brontide matador'] = {
                     "release-group": {
                         "type": "Album",
                         "id": "113ab958-cfb8-4782-99af-639d4d9eae8d",
-                        "primary-type": "Album"
+                        "primary-type": "Album",
                     },
                     "medium-list": [
                         {
@@ -206,22 +199,24 @@ _recordings['search']['brontide matador'] = {
                                     "track_or_recording_length": "366280",
                                     "id": "fe506782-a5cb-3d89-9b3e-86287be05768",
                                     "length": "366280",
-                                    "title": "Matador", "number": "1"
+                                    "title": "Matador",
+                                    "number": "1",
                                 }
                             ],
                             "position": "1",
-                            "track-count": 8
+                            "track-count": 8,
                         }
-                    ]
-                },
-            ]
+                    ],
+                }
+            ],
         }
-    ]
+    ],
 }
 
-_releases = {'search': {}, 'get': {}, 'browse': {}}
-_releases['search']['brontide matador'] = {
-    "release-count": 116, "release-list": [
+_releases = {"search": {}, "get": {}, "browse": {}}
+_releases["search"]["brontide matador"] = {
+    "release-count": 116,
+    "release-list": [
         {
             "ext:score": "100",
             "date": "2009-04-02",
@@ -231,16 +226,16 @@ _releases['search']['brontide matador'] = {
                         "name": "[Worldwide]",
                         "sort-name": "[Worldwide]",
                         "id": "525d4e18-3d00-31b9-a58b-a146a916de8f",
-                        "iso-3166-1-code-list": ["XW"]
+                        "iso-3166-1-code-list": ["XW"],
                     },
-                    "date": "2009-04-02"
+                    "date": "2009-04-02",
                 }
             ],
             "label-info-list": [
                 {
                     "label": {
                         "name": "Holy Roar",
-                        "id": "6e940f35-961d-4ac3-bc2a-569fc211c2e3"
+                        "id": "6e940f35-961d-4ac3-bc2a-569fc211c2e3",
                     }
                 }
             ],
@@ -251,7 +246,7 @@ _releases['search']['brontide matador'] = {
                     "artist": {
                         "name": "Brontide",
                         "sort-name": "Brontide",
-                        "id": "2179fbd2-3c88-4b94-a778-eb3daf1e81a1"
+                        "id": "2179fbd2-3c88-4b94-a778-eb3daf1e81a1",
                     }
                 }
             ],
@@ -265,7 +260,7 @@ _releases['search']['brontide matador'] = {
                 "type": "EP",
                 "secondary-type-list": ["Demo"],
                 "id": "b9207129-2d03-4a68-8a53-3c46fe7d2810",
-                "primary-type": "EP"
+                "primary-type": "EP",
             },
             "medium-list": [
                 {
@@ -273,28 +268,22 @@ _releases['search']['brontide matador'] = {
                     "format": "Digital Media",
                     "disc-count": 0,
                     "track-count": 3,
-                    "track-list": []
+                    "track-list": [],
                 }
             ],
             "medium-count": 1,
-            "text-representation": {
-                "script": "Latn",
-                "language": "eng"
-            }
-        },
-    ]
+            "text-representation": {"script": "Latn", "language": "eng"},
+        }
+    ],
 }
 
-_releases['browse']['Lost in the 80s'] = {
+_releases["browse"]["Lost in the 80s"] = {
     "release-count": 3,
     "release-list": [
         {
             "quality": "normal",
             "status": "Official",
-            "text-representation": {
-                "script": "Latn",
-                "language": "eng"
-            },
+            "text-representation": {"script": "Latn", "language": "eng"},
             "title": "Lost in the 80s",
             "date": "2008-05-06",
             "release-event-count": 1,
@@ -304,14 +293,12 @@ _releases['browse']['Lost in the 80s'] = {
             "release-event-list": [
                 {
                     "area": {
-                        "iso-3166-1-code-list": [
-                            "CA"
-                        ],
+                        "iso-3166-1-code-list": ["CA"],
                         "id": "71bbafaa-e825-3e15-8ca9-017dcad1748b",
                         "name": "Canada",
-                        "sort-name": "Canada"
+                        "sort-name": "Canada",
                     },
-                    "date": "2008-05-06"
+                    "date": "2008-05-06",
                 }
             ],
             "country": "CA",
@@ -319,7 +306,7 @@ _releases['browse']['Lost in the 80s'] = {
                 "back": "false",
                 "artwork": "false",
                 "front": "false",
-                "count": "0"
+                "count": "0",
             },
             "medium-list": [
                 {
@@ -333,11 +320,11 @@ _releases['browse']['Lost in the 80s'] = {
                             "recording": {
                                 "id": "2e0dbf37-65af-4408-8def-7b0b3cb8426b",
                                 "length": "228000",
-                                "title": "Pump Up the Jam"
+                                "title": "Pump Up the Jam",
                             },
                             "track_or_recording_length": "228000",
                             "position": "1",
-                            "number": "1"
+                            "number": "1",
                         },
                         {
                             "id": "01a8cf99-2170-3d3f-96ef-5e4ef7a015a4",
@@ -345,11 +332,11 @@ _releases['browse']['Lost in the 80s'] = {
                             "recording": {
                                 "id": "57017e2e-625d-4e7b-a445-47cdb0224dd2",
                                 "length": "231000",
-                                "title": "You Give Love a Bad Name"
+                                "title": "You Give Love a Bad Name",
                             },
                             "track_or_recording_length": "231000",
                             "position": "2",
-                            "number": "2"
+                            "number": "2",
                         },
                         {
                             "id": "375a7ce7-5a41-3fbf-9809-96d491401034",
@@ -357,11 +344,11 @@ _releases['browse']['Lost in the 80s'] = {
                             "recording": {
                                 "id": "a948672b-b42d-44a5-89b0-7e9ab6a7e11d",
                                 "length": "189000",
-                                "title": "You Shook Me All Night Long"
+                                "title": "You Shook Me All Night Long",
                             },
                             "track_or_recording_length": "189000",
                             "position": "3",
-                            "number": "3"
+                            "number": "3",
                         },
                         {
                             "id": "ed7d823e-76da-31be-82a8-770288e27d32",
@@ -369,11 +356,11 @@ _releases['browse']['Lost in the 80s'] = {
                             "recording": {
                                 "id": "6e097e31-f37b-4fae-8ad0-ada57f3091a7",
                                 "length": "253000",
-                                "title": "Incognito"
+                                "title": "Incognito",
                             },
                             "track_or_recording_length": "253000",
                             "position": "4",
-                            "number": "4"
+                            "number": "4",
                         },
                         {
                             "id": "76ac8c77-6a99-34d9-ae4d-be8f056d50e0",
@@ -381,11 +368,11 @@ _releases['browse']['Lost in the 80s'] = {
                             "recording": {
                                 "id": "faa922e6-e834-44ee-8125-79e640a690e3",
                                 "length": "221000",
-                                "title": "Touch Me"
+                                "title": "Touch Me",
                             },
                             "track_or_recording_length": "221000",
                             "position": "5",
-                            "number": "5"
+                            "number": "5",
                         },
                         {
                             "id": "d0a87409-2be6-3ab7-8526-4313e7134be1",
@@ -393,11 +380,11 @@ _releases['browse']['Lost in the 80s'] = {
                             "recording": {
                                 "id": "02da8148-60d8-4c79-ab31-8d90d233d711",
                                 "length": "228000",
-                                "title": "Part-Time Lover"
+                                "title": "Part-Time Lover",
                             },
                             "track_or_recording_length": "228000",
                             "position": "6",
-                            "number": "6"
+                            "number": "6",
                         },
                         {
                             "id": "02c5384b-5ca9-38e9-8b7c-c08dce608deb",
@@ -405,11 +392,11 @@ _releases['browse']['Lost in the 80s'] = {
                             "recording": {
                                 "id": "40085704-d6ab-44f6-a4d8-b27c9ca25b31",
                                 "length": "248000",
-                                "title": "Fresh"
+                                "title": "Fresh",
                             },
                             "track_or_recording_length": "248000",
                             "position": "7",
-                            "number": "7"
+                            "number": "7",
                         },
                         {
                             "id": "ab389542-53d5-346a-b168-1d915ecf0ef6",
@@ -417,11 +404,11 @@ _releases['browse']['Lost in the 80s'] = {
                             "recording": {
                                 "id": "77edd338-eeaf-4157-9e2a-5cc3bcee8abd",
                                 "length": "257000",
-                                "title": "Billie Jean"
+                                "title": "Billie Jean",
                             },
                             "track_or_recording_length": "257000",
                             "position": "8",
-                            "number": "8"
+                            "number": "8",
                         },
                         {
                             "id": "6d9e722b-7408-350e-bb7c-2de1e329ae84",
@@ -429,11 +416,11 @@ _releases['browse']['Lost in the 80s'] = {
                             "recording": {
                                 "id": "040aaffa-7206-40ff-9930-469413fe2420",
                                 "length": "293000",
-                                "title": "Careless Whisper"
+                                "title": "Careless Whisper",
                             },
                             "track_or_recording_length": "293000",
                             "position": "9",
-                            "number": "9"
+                            "number": "9",
                         },
                         {
                             "id": "63b4e67c-7536-3cd0-8c47-0310c1e40866",
@@ -441,11 +428,11 @@ _releases['browse']['Lost in the 80s'] = {
                             "recording": {
                                 "id": "054942f0-4c0f-4e92-a606-d590976b1cff",
                                 "length": "211000",
-                                "title": "Tainted Love"
+                                "title": "Tainted Love",
                             },
                             "track_or_recording_length": "211000",
                             "position": "10",
-                            "number": "10"
+                            "number": "10",
                         },
                         {
                             "id": "a07f4ca3-dbf0-3337-a247-afcd0509334a",
@@ -453,11 +440,11 @@ _releases['browse']['Lost in the 80s'] = {
                             "recording": {
                                 "id": "8023b5ad-649a-4c67-b7a2-e12358606f6e",
                                 "length": "245000",
-                                "title": "Straight Up"
+                                "title": "Straight Up",
                             },
                             "track_or_recording_length": "245000",
                             "position": "11",
-                            "number": "11"
+                            "number": "11",
                         },
                         {
                             "id": "73d47f16-b18d-36ff-b0bb-1fa1fd32ebf7",
@@ -465,18 +452,18 @@ _releases['browse']['Lost in the 80s'] = {
                             "recording": {
                                 "id": "95a8c8a1-fcb6-4cbb-a853-be86d816b357",
                                 "length": "322000",
-                                "title": "Black Velvet"
+                                "title": "Black Velvet",
                             },
                             "track_or_recording_length": "322000",
                             "position": "12",
-                            "number": "12"
-                        }
-                    ]
+                            "number": "12",
+                        },
+                    ],
                 }
             ],
-            "asin": "B0017M8YTO"
-        },
-    ]
+            "asin": "B0017M8YTO",
+        }
+    ],
 }
 
 
diff --git a/api/tests/musicbrainz/test_api.py b/api/tests/musicbrainz/test_api.py
index fdd1dbdb03..de84813f1f 100644
--- a/api/tests/musicbrainz/test_api.py
+++ b/api/tests/musicbrainz/test_api.py
@@ -4,89 +4,95 @@ from django.urls import reverse
 from funkwhale_api.musicbrainz import api
 
 
-
 def test_can_search_recording_in_musicbrainz_api(
-        recordings, db, mocker, logged_in_api_client):
+    recordings, db, mocker, logged_in_api_client
+):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.recordings.search',
-        return_value=recordings['search']['brontide matador'])
-    query = 'brontide matador'
-    url = reverse('api:v1:providers:musicbrainz:search-recordings')
-    expected = recordings['search']['brontide matador']
-    response = logged_in_api_client.get(url, data={'query': query})
+        "funkwhale_api.musicbrainz.api.recordings.search",
+        return_value=recordings["search"]["brontide matador"],
+    )
+    query = "brontide matador"
+    url = reverse("api:v1:providers:musicbrainz:search-recordings")
+    expected = recordings["search"]["brontide matador"]
+    response = logged_in_api_client.get(url, data={"query": query})
 
     assert expected == response.data
 
 
-def test_can_search_release_in_musicbrainz_api(releases, db, mocker, logged_in_api_client):
+def test_can_search_release_in_musicbrainz_api(
+    releases, db, mocker, logged_in_api_client
+):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.releases.search',
-        return_value=releases['search']['brontide matador'])
-    query = 'brontide matador'
-    url = reverse('api:v1:providers:musicbrainz:search-releases')
-    expected = releases['search']['brontide matador']
-    response = logged_in_api_client.get(url, data={'query': query})
+        "funkwhale_api.musicbrainz.api.releases.search",
+        return_value=releases["search"]["brontide matador"],
+    )
+    query = "brontide matador"
+    url = reverse("api:v1:providers:musicbrainz:search-releases")
+    expected = releases["search"]["brontide matador"]
+    response = logged_in_api_client.get(url, data={"query": query})
 
     assert expected == response.data
 
 
-def test_can_search_artists_in_musicbrainz_api(artists, db, mocker, logged_in_api_client):
+def test_can_search_artists_in_musicbrainz_api(
+    artists, db, mocker, logged_in_api_client
+):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.artists.search',
-        return_value=artists['search']['lost fingers'])
-    query = 'lost fingers'
-    url = reverse('api:v1:providers:musicbrainz:search-artists')
-    expected = artists['search']['lost fingers']
-    response = logged_in_api_client.get(url, data={'query': query})
+        "funkwhale_api.musicbrainz.api.artists.search",
+        return_value=artists["search"]["lost fingers"],
+    )
+    query = "lost fingers"
+    url = reverse("api:v1:providers:musicbrainz:search-artists")
+    expected = artists["search"]["lost fingers"]
+    response = logged_in_api_client.get(url, data={"query": query})
 
     assert expected == response.data
 
 
 def test_can_get_artist_in_musicbrainz_api(artists, db, mocker, logged_in_api_client):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.artists.get',
-        return_value=artists['get']['lost fingers'])
-    uuid = 'ac16bbc0-aded-4477-a3c3-1d81693d58c9'
-    url = reverse('api:v1:providers:musicbrainz:artist-detail', kwargs={
-        'uuid': uuid,
-    })
+        "funkwhale_api.musicbrainz.api.artists.get",
+        return_value=artists["get"]["lost fingers"],
+    )
+    uuid = "ac16bbc0-aded-4477-a3c3-1d81693d58c9"
+    url = reverse("api:v1:providers:musicbrainz:artist-detail", kwargs={"uuid": uuid})
     response = logged_in_api_client.get(url)
-    expected = artists['get']['lost fingers']
+    expected = artists["get"]["lost fingers"]
 
     assert expected == response.data
 
 
 def test_can_broswe_release_group_using_musicbrainz_api(
-        release_groups, db, mocker, logged_in_api_client):
+    release_groups, db, mocker, logged_in_api_client
+):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.release_groups.browse',
-        return_value=release_groups['browse']['lost fingers'])
-    uuid = 'ac16bbc0-aded-4477-a3c3-1d81693d58c9'
+        "funkwhale_api.musicbrainz.api.release_groups.browse",
+        return_value=release_groups["browse"]["lost fingers"],
+    )
+    uuid = "ac16bbc0-aded-4477-a3c3-1d81693d58c9"
     url = reverse(
-        'api:v1:providers:musicbrainz:release-group-browse',
-        kwargs={
-            'artist_uuid': uuid,
-        }
+        "api:v1:providers:musicbrainz:release-group-browse",
+        kwargs={"artist_uuid": uuid},
     )
     response = logged_in_api_client.get(url)
-    expected = release_groups['browse']['lost fingers']
+    expected = release_groups["browse"]["lost fingers"]
 
     assert expected == response.data
 
 
 def test_can_broswe_releases_using_musicbrainz_api(
-        releases, db, mocker, logged_in_api_client):
+    releases, db, mocker, logged_in_api_client
+):
     mocker.patch(
-        'funkwhale_api.musicbrainz.api.releases.browse',
-        return_value=releases['browse']['Lost in the 80s'])
-    uuid = 'f04ed607-11b7-3843-957e-503ecdd485d1'
+        "funkwhale_api.musicbrainz.api.releases.browse",
+        return_value=releases["browse"]["Lost in the 80s"],
+    )
+    uuid = "f04ed607-11b7-3843-957e-503ecdd485d1"
     url = reverse(
-        'api:v1:providers:musicbrainz:release-browse',
-        kwargs={
-            'release_group_uuid': uuid,
-        }
+        "api:v1:providers:musicbrainz:release-browse",
+        kwargs={"release_group_uuid": uuid},
     )
     response = logged_in_api_client.get(url)
-    expected = releases['browse']['Lost in the 80s']
+    expected = releases["browse"]["Lost in the 80s"]
 
     assert expected == response.data
diff --git a/api/tests/musicbrainz/test_cache.py b/api/tests/musicbrainz/test_cache.py
index fe0d567730..3a326ff24e 100644
--- a/api/tests/musicbrainz/test_cache.py
+++ b/api/tests/musicbrainz/test_cache.py
@@ -2,12 +2,12 @@ from funkwhale_api.musicbrainz import client
 
 
 def test_can_search_recording_in_musicbrainz_api(mocker):
-    r = {'hello': 'world'}
+    r = {"hello": "world"}
     m = mocker.patch(
-        'funkwhale_api.musicbrainz.client._api.search_artists',
-        return_value=r)
-    assert client.api.artists.search('test') == r
+        "funkwhale_api.musicbrainz.client._api.search_artists", return_value=r
+    )
+    assert client.api.artists.search("test") == r
     # now call from cache
-    assert client.api.artists.search('test') == r
-    assert client.api.artists.search('test') == r
+    assert client.api.artists.search("test") == r
+    assert client.api.artists.search("test") == r
     assert m.call_count == 1
diff --git a/api/tests/playlists/test_models.py b/api/tests/playlists/test_models.py
index fe5dd40a85..3258e6d10d 100644
--- a/api/tests/playlists/test_models.py
+++ b/api/tests/playlists/test_models.py
@@ -4,7 +4,7 @@ from rest_framework import exceptions
 
 
 def test_can_insert_plt(factories):
-    plt = factories['playlists.PlaylistTrack']()
+    plt = factories["playlists.PlaylistTrack"]()
     modification_date = plt.playlist.modification_date
 
     assert plt.index is None
@@ -17,9 +17,8 @@ def test_can_insert_plt(factories):
 
 
 def test_insert_use_last_idx_by_default(factories):
-    playlist = factories['playlists.Playlist']()
-    plts = factories['playlists.PlaylistTrack'].create_batch(
-        size=3, playlist=playlist)
+    playlist = factories["playlists.Playlist"]()
+    plts = factories["playlists.PlaylistTrack"].create_batch(size=3, playlist=playlist)
 
     for i, plt in enumerate(plts):
         index = playlist.insert(plt)
@@ -28,11 +27,12 @@ def test_insert_use_last_idx_by_default(factories):
         assert index == i
         assert plt.index == i
 
+
 def test_can_insert_at_index(factories):
-    playlist = factories['playlists.Playlist']()
-    first = factories['playlists.PlaylistTrack'](playlist=playlist)
+    playlist = factories["playlists.Playlist"]()
+    first = factories["playlists.PlaylistTrack"](playlist=playlist)
     playlist.insert(first)
-    new_first = factories['playlists.PlaylistTrack'](playlist=playlist)
+    new_first = factories["playlists.PlaylistTrack"](playlist=playlist)
     index = playlist.insert(new_first, index=0)
     first.refresh_from_db()
     new_first.refresh_from_db()
@@ -43,10 +43,10 @@ def test_can_insert_at_index(factories):
 
 
 def test_can_insert_and_move(factories):
-    playlist = factories['playlists.Playlist']()
-    first = factories['playlists.PlaylistTrack'](playlist=playlist, index=0)
-    second = factories['playlists.PlaylistTrack'](playlist=playlist, index=1)
-    third = factories['playlists.PlaylistTrack'](playlist=playlist, index=2)
+    playlist = factories["playlists.Playlist"]()
+    first = factories["playlists.PlaylistTrack"](playlist=playlist, index=0)
+    second = factories["playlists.PlaylistTrack"](playlist=playlist, index=1)
+    third = factories["playlists.PlaylistTrack"](playlist=playlist, index=2)
 
     playlist.insert(second, index=0)
 
@@ -60,10 +60,10 @@ def test_can_insert_and_move(factories):
 
 
 def test_can_insert_and_move_last_to_0(factories):
-    playlist = factories['playlists.Playlist']()
-    first = factories['playlists.PlaylistTrack'](playlist=playlist, index=0)
-    second = factories['playlists.PlaylistTrack'](playlist=playlist, index=1)
-    third = factories['playlists.PlaylistTrack'](playlist=playlist, index=2)
+    playlist = factories["playlists.Playlist"]()
+    first = factories["playlists.PlaylistTrack"](playlist=playlist, index=0)
+    second = factories["playlists.PlaylistTrack"](playlist=playlist, index=1)
+    third = factories["playlists.PlaylistTrack"](playlist=playlist, index=2)
 
     playlist.insert(third, index=0)
 
@@ -77,24 +77,24 @@ def test_can_insert_and_move_last_to_0(factories):
 
 
 def test_cannot_insert_at_wrong_index(factories):
-    plt = factories['playlists.PlaylistTrack']()
-    new = factories['playlists.PlaylistTrack'](playlist=plt.playlist)
+    plt = factories["playlists.PlaylistTrack"]()
+    new = factories["playlists.PlaylistTrack"](playlist=plt.playlist)
     with pytest.raises(exceptions.ValidationError):
         plt.playlist.insert(new, 2)
 
 
 def test_cannot_insert_at_negative_index(factories):
-    plt = factories['playlists.PlaylistTrack']()
-    new = factories['playlists.PlaylistTrack'](playlist=plt.playlist)
+    plt = factories["playlists.PlaylistTrack"]()
+    new = factories["playlists.PlaylistTrack"](playlist=plt.playlist)
     with pytest.raises(exceptions.ValidationError):
         plt.playlist.insert(new, -1)
 
 
 def test_remove_update_indexes(factories):
-    playlist = factories['playlists.Playlist']()
-    first = factories['playlists.PlaylistTrack'](playlist=playlist, index=0)
-    second = factories['playlists.PlaylistTrack'](playlist=playlist, index=1)
-    third = factories['playlists.PlaylistTrack'](playlist=playlist, index=2)
+    playlist = factories["playlists.Playlist"]()
+    first = factories["playlists.PlaylistTrack"](playlist=playlist, index=0)
+    second = factories["playlists.PlaylistTrack"](playlist=playlist, index=1)
+    third = factories["playlists.PlaylistTrack"](playlist=playlist, index=2)
 
     second.delete(update_indexes=True)
 
@@ -106,9 +106,9 @@ def test_remove_update_indexes(factories):
 
 
 def test_can_insert_many(factories):
-    playlist = factories['playlists.Playlist']()
-    existing = factories['playlists.PlaylistTrack'](playlist=playlist, index=0)
-    tracks = factories['music.Track'].create_batch(size=3)
+    playlist = factories["playlists.Playlist"]()
+    existing = factories["playlists.PlaylistTrack"](playlist=playlist, index=0)
+    tracks = factories["music.Track"].create_batch(size=3)
     plts = playlist.insert_many(tracks)
     for i, plt in enumerate(plts):
         assert plt.index == i + 1
@@ -117,10 +117,9 @@ def test_can_insert_many(factories):
 
 
 def test_insert_many_honor_max_tracks(preferences, factories):
-    preferences['playlists__max_tracks'] = 4
-    playlist = factories['playlists.Playlist']()
-    plts = factories['playlists.PlaylistTrack'].create_batch(
-        size=2, playlist=playlist)
-    track = factories['music.Track']()
+    preferences["playlists__max_tracks"] = 4
+    playlist = factories["playlists.Playlist"]()
+    plts = factories["playlists.PlaylistTrack"].create_batch(size=2, playlist=playlist)
+    track = factories["music.Track"]()
     with pytest.raises(exceptions.ValidationError):
         playlist.insert_many([track, track, track])
diff --git a/api/tests/playlists/test_serializers.py b/api/tests/playlists/test_serializers.py
index 908c1c7964..05e5bd7cab 100644
--- a/api/tests/playlists/test_serializers.py
+++ b/api/tests/playlists/test_serializers.py
@@ -3,29 +3,25 @@ from funkwhale_api.playlists import serializers
 
 
 def test_cannot_max_500_tracks_per_playlist(factories, preferences):
-    preferences['playlists__max_tracks'] = 2
-    playlist = factories['playlists.Playlist']()
-    plts = factories['playlists.PlaylistTrack'].create_batch(
-        size=2, playlist=playlist)
-    track = factories['music.Track']()
-    serializer = serializers.PlaylistTrackWriteSerializer(data={
-        'playlist': playlist.pk,
-        'track': track.pk,
-    })
+    preferences["playlists__max_tracks"] = 2
+    playlist = factories["playlists.Playlist"]()
+    plts = factories["playlists.PlaylistTrack"].create_batch(size=2, playlist=playlist)
+    track = factories["music.Track"]()
+    serializer = serializers.PlaylistTrackWriteSerializer(
+        data={"playlist": playlist.pk, "track": track.pk}
+    )
 
     assert serializer.is_valid() is False
-    assert 'playlist' in serializer.errors
+    assert "playlist" in serializer.errors
 
 
 def test_create_insert_is_called_when_index_is_None(factories, mocker):
-    insert = mocker.spy(models.Playlist, 'insert')
-    playlist = factories['playlists.Playlist']()
-    track = factories['music.Track']()
-    serializer = serializers.PlaylistTrackWriteSerializer(data={
-        'playlist': playlist.pk,
-        'track': track.pk,
-        'index': None,
-    })
+    insert = mocker.spy(models.Playlist, "insert")
+    playlist = factories["playlists.Playlist"]()
+    track = factories["music.Track"]()
+    serializer = serializers.PlaylistTrackWriteSerializer(
+        data={"playlist": playlist.pk, "track": track.pk, "index": None}
+    )
     assert serializer.is_valid() is True
 
     plt = serializer.save()
@@ -34,16 +30,14 @@ def test_create_insert_is_called_when_index_is_None(factories, mocker):
 
 
 def test_create_insert_is_called_when_index_is_provided(factories, mocker):
-    playlist = factories['playlists.Playlist']()
-    first = factories['playlists.PlaylistTrack'](playlist=playlist, index=0)
-    insert = mocker.spy(models.Playlist, 'insert')
-    factories['playlists.Playlist']()
-    track = factories['music.Track']()
-    serializer = serializers.PlaylistTrackWriteSerializer(data={
-        'playlist': playlist.pk,
-        'track': track.pk,
-        'index': 0,
-    })
+    playlist = factories["playlists.Playlist"]()
+    first = factories["playlists.PlaylistTrack"](playlist=playlist, index=0)
+    insert = mocker.spy(models.Playlist, "insert")
+    factories["playlists.Playlist"]()
+    track = factories["music.Track"]()
+    serializer = serializers.PlaylistTrackWriteSerializer(
+        data={"playlist": playlist.pk, "track": track.pk, "index": 0}
+    )
     assert serializer.is_valid() is True
 
     plt = serializer.save()
@@ -54,17 +48,15 @@ def test_create_insert_is_called_when_index_is_provided(factories, mocker):
 
 
 def test_update_insert_is_called_when_index_is_provided(factories, mocker):
-    playlist = factories['playlists.Playlist']()
-    first = factories['playlists.PlaylistTrack'](playlist=playlist, index=0)
-    second = factories['playlists.PlaylistTrack'](playlist=playlist, index=1)
-    insert = mocker.spy(models.Playlist, 'insert')
-    factories['playlists.Playlist']()
-    track = factories['music.Track']()
-    serializer = serializers.PlaylistTrackWriteSerializer(second, data={
-        'playlist': playlist.pk,
-        'track': second.track.pk,
-        'index': 0,
-    })
+    playlist = factories["playlists.Playlist"]()
+    first = factories["playlists.PlaylistTrack"](playlist=playlist, index=0)
+    second = factories["playlists.PlaylistTrack"](playlist=playlist, index=1)
+    insert = mocker.spy(models.Playlist, "insert")
+    factories["playlists.Playlist"]()
+    track = factories["music.Track"]()
+    serializer = serializers.PlaylistTrackWriteSerializer(
+        second, data={"playlist": playlist.pk, "track": second.track.pk, "index": 0}
+    )
     assert serializer.is_valid() is True
 
     plt = serializer.save()
diff --git a/api/tests/playlists/test_views.py b/api/tests/playlists/test_views.py
index 44d0608210..7835a72588 100644
--- a/api/tests/playlists/test_views.py
+++ b/api/tests/playlists/test_views.py
@@ -10,63 +10,55 @@ from funkwhale_api.playlists import serializers
 
 
 def test_can_create_playlist_via_api(logged_in_api_client):
-    url = reverse('api:v1:playlists-list')
-    data = {
-        'name': 'test',
-        'privacy_level': 'everyone'
-    }
+    url = reverse("api:v1:playlists-list")
+    data = {"name": "test", "privacy_level": "everyone"}
 
     response = logged_in_api_client.post(url, data)
 
-    playlist = logged_in_api_client.user.playlists.latest('id')
-    assert playlist.name == 'test'
-    assert playlist.privacy_level == 'everyone'
+    playlist = logged_in_api_client.user.playlists.latest("id")
+    assert playlist.name == "test"
+    assert playlist.privacy_level == "everyone"
 
 
 def test_serializer_includes_tracks_count(factories, logged_in_api_client):
-    playlist = factories['playlists.Playlist']()
-    plt = factories['playlists.PlaylistTrack'](playlist=playlist)
+    playlist = factories["playlists.Playlist"]()
+    plt = factories["playlists.PlaylistTrack"](playlist=playlist)
 
-    url = reverse('api:v1:playlists-detail', kwargs={'pk': playlist.pk})
+    url = reverse("api:v1:playlists-detail", kwargs={"pk": playlist.pk})
     response = logged_in_api_client.get(url)
 
-    assert response.data['tracks_count'] == 1
+    assert response.data["tracks_count"] == 1
 
 
 def test_playlist_inherits_user_privacy(logged_in_api_client):
-    url = reverse('api:v1:playlists-list')
+    url = reverse("api:v1:playlists-list")
     user = logged_in_api_client.user
-    user.privacy_level = 'me'
+    user.privacy_level = "me"
     user.save()
 
-    data = {
-        'name': 'test',
-    }
+    data = {"name": "test"}
 
     response = logged_in_api_client.post(url, data)
-    playlist = user.playlists.latest('id')
+    playlist = user.playlists.latest("id")
     assert playlist.privacy_level == user.privacy_level
 
 
 def test_can_add_playlist_track_via_api(factories, logged_in_api_client):
-    tracks = factories['music.Track'].create_batch(5)
-    playlist = factories['playlists.Playlist'](user=logged_in_api_client.user)
-    url = reverse('api:v1:playlist-tracks-list')
-    data = {
-        'playlist': playlist.pk,
-        'track': tracks[0].pk
-    }
+    tracks = factories["music.Track"].create_batch(5)
+    playlist = factories["playlists.Playlist"](user=logged_in_api_client.user)
+    url = reverse("api:v1:playlist-tracks-list")
+    data = {"playlist": playlist.pk, "track": tracks[0].pk}
 
     response = logged_in_api_client.post(url, data)
     assert response.status_code == 201
-    plts = logged_in_api_client.user.playlists.latest('id').playlist_tracks.all()
+    plts = logged_in_api_client.user.playlists.latest("id").playlist_tracks.all()
     assert plts.first().track == tracks[0]
 
 
-@pytest.mark.parametrize('name,method', [
-    ('api:v1:playlist-tracks-list', 'post'),
-    ('api:v1:playlists-list', 'post'),
-])
+@pytest.mark.parametrize(
+    "name,method",
+    [("api:v1:playlist-tracks-list", "post"), ("api:v1:playlists-list", "post")],
+)
 def test_url_requires_login(name, method, factories, api_client):
     url = reverse(name)
 
@@ -75,29 +67,24 @@ def test_url_requires_login(name, method, factories, api_client):
     assert response.status_code == 401
 
 
-def test_only_can_add_track_on_own_playlist_via_api(
-        factories, logged_in_api_client):
-    track = factories['music.Track']()
-    playlist = factories['playlists.Playlist']()
-    url = reverse('api:v1:playlist-tracks-list')
-    data = {
-        'playlist': playlist.pk,
-        'track': track.pk
-    }
+def test_only_can_add_track_on_own_playlist_via_api(factories, logged_in_api_client):
+    track = factories["music.Track"]()
+    playlist = factories["playlists.Playlist"]()
+    url = reverse("api:v1:playlist-tracks-list")
+    data = {"playlist": playlist.pk, "track": track.pk}
 
     response = logged_in_api_client.post(url, data)
     assert response.status_code == 400
     assert playlist.playlist_tracks.count() == 0
 
 
-def test_deleting_plt_updates_indexes(
-        mocker, factories, logged_in_api_client):
-    remove = mocker.spy(models.Playlist, 'remove')
-    track = factories['music.Track']()
-    plt = factories['playlists.PlaylistTrack'](
-        index=0,
-        playlist__user=logged_in_api_client.user)
-    url = reverse('api:v1:playlist-tracks-detail', kwargs={'pk': plt.pk})
+def test_deleting_plt_updates_indexes(mocker, factories, logged_in_api_client):
+    remove = mocker.spy(models.Playlist, "remove")
+    track = factories["music.Track"]()
+    plt = factories["playlists.PlaylistTrack"](
+        index=0, playlist__user=logged_in_api_client.user
+    )
+    url = reverse("api:v1:playlist-tracks-detail", kwargs={"pk": plt.pk})
 
     response = logged_in_api_client.delete(url)
 
@@ -105,97 +92,93 @@ def test_deleting_plt_updates_indexes(
     remove.assert_called_once_with(plt.playlist, 0)
 
 
-@pytest.mark.parametrize('level', ['instance', 'me', 'followers'])
+@pytest.mark.parametrize("level", ["instance", "me", "followers"])
 def test_playlist_privacy_respected_in_list_anon(
-        preferences, level, factories, api_client):
-    preferences['common__api_authentication_required'] = False
-    factories['playlists.Playlist'](privacy_level=level)
-    url = reverse('api:v1:playlists-list')
+    preferences, level, factories, api_client
+):
+    preferences["common__api_authentication_required"] = False
+    factories["playlists.Playlist"](privacy_level=level)
+    url = reverse("api:v1:playlists-list")
     response = api_client.get(url)
 
-    assert response.data['count'] == 0
+    assert response.data["count"] == 0
 
 
-@pytest.mark.parametrize('method', ['PUT', 'PATCH', 'DELETE'])
+@pytest.mark.parametrize("method", ["PUT", "PATCH", "DELETE"])
 def test_only_owner_can_edit_playlist(method, factories, logged_in_api_client):
-    playlist = factories['playlists.Playlist']()
-    url = reverse('api:v1:playlists-detail', kwargs={'pk': playlist.pk})
+    playlist = factories["playlists.Playlist"]()
+    url = reverse("api:v1:playlists-detail", kwargs={"pk": playlist.pk})
     response = getattr(logged_in_api_client, method.lower())(url)
 
     assert response.status_code == 404
 
 
-@pytest.mark.parametrize('method', ['PUT', 'PATCH', 'DELETE'])
-def test_only_owner_can_edit_playlist_track(
-        method, factories, logged_in_api_client):
-    plt = factories['playlists.PlaylistTrack']()
-    url = reverse('api:v1:playlist-tracks-detail', kwargs={'pk': plt.pk})
+@pytest.mark.parametrize("method", ["PUT", "PATCH", "DELETE"])
+def test_only_owner_can_edit_playlist_track(method, factories, logged_in_api_client):
+    plt = factories["playlists.PlaylistTrack"]()
+    url = reverse("api:v1:playlist-tracks-detail", kwargs={"pk": plt.pk})
     response = getattr(logged_in_api_client, method.lower())(url)
 
     assert response.status_code == 404
 
 
-@pytest.mark.parametrize('level', ['instance', 'me', 'followers'])
+@pytest.mark.parametrize("level", ["instance", "me", "followers"])
 def test_playlist_track_privacy_respected_in_list_anon(
-        level, factories, api_client, preferences):
-    preferences['common__api_authentication_required'] = False
-    factories['playlists.PlaylistTrack'](playlist__privacy_level=level)
-    url = reverse('api:v1:playlist-tracks-list')
+    level, factories, api_client, preferences
+):
+    preferences["common__api_authentication_required"] = False
+    factories["playlists.PlaylistTrack"](playlist__privacy_level=level)
+    url = reverse("api:v1:playlist-tracks-list")
     response = api_client.get(url)
 
-    assert response.data['count'] == 0
+    assert response.data["count"] == 0
 
 
-@pytest.mark.parametrize('level', ['instance', 'me', 'followers'])
-def test_can_list_tracks_from_playlist(
-        level, factories, logged_in_api_client):
-    plt = factories['playlists.PlaylistTrack'](
-        playlist__user=logged_in_api_client.user)
-    url = reverse('api:v1:playlists-tracks', kwargs={'pk': plt.playlist.pk})
+@pytest.mark.parametrize("level", ["instance", "me", "followers"])
+def test_can_list_tracks_from_playlist(level, factories, logged_in_api_client):
+    plt = factories["playlists.PlaylistTrack"](playlist__user=logged_in_api_client.user)
+    url = reverse("api:v1:playlists-tracks", kwargs={"pk": plt.playlist.pk})
     response = logged_in_api_client.get(url)
     serialized_plt = serializers.PlaylistTrackSerializer(plt).data
 
-    assert response.data['count'] == 1
-    assert response.data['results'][0] == serialized_plt
+    assert response.data["count"] == 1
+    assert response.data["results"][0] == serialized_plt
 
 
 def test_can_add_multiple_tracks_at_once_via_api(
-        factories, mocker, logged_in_api_client):
-    playlist = factories['playlists.Playlist'](user=logged_in_api_client.user)
-    tracks = factories['music.Track'].create_batch(size=5)
+    factories, mocker, logged_in_api_client
+):
+    playlist = factories["playlists.Playlist"](user=logged_in_api_client.user)
+    tracks = factories["music.Track"].create_batch(size=5)
     track_ids = [t.id for t in tracks]
-    mocker.spy(playlist, 'insert_many')
-    url = reverse('api:v1:playlists-add', kwargs={'pk': playlist.pk})
-    response = logged_in_api_client.post(url, {'tracks': track_ids})
+    mocker.spy(playlist, "insert_many")
+    url = reverse("api:v1:playlists-add", kwargs={"pk": playlist.pk})
+    response = logged_in_api_client.post(url, {"tracks": track_ids})
 
     assert response.status_code == 201
     assert playlist.playlist_tracks.count() == len(track_ids)
 
-    for plt in playlist.playlist_tracks.order_by('index'):
-        assert response.data['results'][plt.index]['id'] == plt.id
+    for plt in playlist.playlist_tracks.order_by("index"):
+        assert response.data["results"][plt.index]["id"] == plt.id
         assert plt.track == tracks[plt.index]
 
 
-def test_can_clear_playlist_from_api(
-        factories, mocker, logged_in_api_client):
-    playlist = factories['playlists.Playlist'](user=logged_in_api_client.user)
-    plts = factories['playlists.PlaylistTrack'].create_batch(
-        size=5, playlist=playlist)
-    url = reverse('api:v1:playlists-clear', kwargs={'pk': playlist.pk})
+def test_can_clear_playlist_from_api(factories, mocker, logged_in_api_client):
+    playlist = factories["playlists.Playlist"](user=logged_in_api_client.user)
+    plts = factories["playlists.PlaylistTrack"].create_batch(size=5, playlist=playlist)
+    url = reverse("api:v1:playlists-clear", kwargs={"pk": playlist.pk})
     response = logged_in_api_client.delete(url)
 
     assert response.status_code == 204
     assert playlist.playlist_tracks.count() == 0
 
 
-def test_update_playlist_from_api(
-        factories, mocker, logged_in_api_client):
-    playlist = factories['playlists.Playlist'](user=logged_in_api_client.user)
-    plts = factories['playlists.PlaylistTrack'].create_batch(
-        size=5, playlist=playlist)
-    url = reverse('api:v1:playlists-detail', kwargs={'pk': playlist.pk})
-    response = logged_in_api_client.patch(url, {'name': 'test'})
+def test_update_playlist_from_api(factories, mocker, logged_in_api_client):
+    playlist = factories["playlists.Playlist"](user=logged_in_api_client.user)
+    plts = factories["playlists.PlaylistTrack"].create_batch(size=5, playlist=playlist)
+    url = reverse("api:v1:playlists-detail", kwargs={"pk": playlist.pk})
+    response = logged_in_api_client.patch(url, {"name": "test"})
     playlist.refresh_from_db()
 
     assert response.status_code == 200
-    assert response.data['user']['username'] == playlist.user.username
+    assert response.data["user"]["username"] == playlist.user.username
diff --git a/api/tests/radios/test_api.py b/api/tests/radios/test_api.py
index 66bf6052d5..4a72d81ba0 100644
--- a/api/tests/radios/test_api.py
+++ b/api/tests/radios/test_api.py
@@ -9,151 +9,125 @@ from funkwhale_api.radios import serializers
 
 
 def test_can_list_config_options(logged_in_client):
-    url = reverse('api:v1:radios:radios-filters')
+    url = reverse("api:v1:radios:radios-filters")
     response = logged_in_client.get(url)
 
     assert response.status_code == 200
 
-    payload = json.loads(response.content.decode('utf-8'))
+    payload = json.loads(response.content.decode("utf-8"))
 
     expected = [f for f in filters.registry.values() if f.expose_in_api]
     assert len(payload) == len(expected)
 
 
 def test_can_validate_config(logged_in_client, factories):
-    artist1 = factories['music.Artist']()
-    artist2 = factories['music.Artist']()
-    factories['music.Track'].create_batch(3, artist=artist1)
-    factories['music.Track'].create_batch(3, artist=artist2)
-    candidates = artist1.tracks.order_by('pk')
-    f = {
-        'filters': [
-            {'type': 'artist', 'ids': [artist1.pk]}
-        ]
-    }
-    url = reverse('api:v1:radios:radios-validate')
+    artist1 = factories["music.Artist"]()
+    artist2 = factories["music.Artist"]()
+    factories["music.Track"].create_batch(3, artist=artist1)
+    factories["music.Track"].create_batch(3, artist=artist2)
+    candidates = artist1.tracks.order_by("pk")
+    f = {"filters": [{"type": "artist", "ids": [artist1.pk]}]}
+    url = reverse("api:v1:radios:radios-validate")
     response = logged_in_client.post(
-        url,
-        json.dumps(f),
-        content_type="application/json")
+        url, json.dumps(f), content_type="application/json"
+    )
 
     assert response.status_code == 200
 
-    payload = json.loads(response.content.decode('utf-8'))
+    payload = json.loads(response.content.decode("utf-8"))
 
     expected = {
-        'count': candidates.count(),
-        'sample': TrackSerializer(candidates, many=True).data
+        "count": candidates.count(),
+        "sample": TrackSerializer(candidates, many=True).data,
     }
-    assert payload['filters'][0]['candidates'] == expected
-    assert payload['filters'][0]['errors'] == []
+    assert payload["filters"][0]["candidates"] == expected
+    assert payload["filters"][0]["errors"] == []
 
 
 def test_can_validate_config_with_wrong_config(logged_in_client, factories):
-    f = {
-        'filters': [
-            {'type': 'artist', 'ids': [999]}
-        ]
-    }
-    url = reverse('api:v1:radios:radios-validate')
+    f = {"filters": [{"type": "artist", "ids": [999]}]}
+    url = reverse("api:v1:radios:radios-validate")
     response = logged_in_client.post(
-        url,
-        json.dumps(f),
-        content_type="application/json")
+        url, json.dumps(f), content_type="application/json"
+    )
 
     assert response.status_code == 200
 
-    payload = json.loads(response.content.decode('utf-8'))
+    payload = json.loads(response.content.decode("utf-8"))
 
-    expected = {
-        'count': None,
-        'sample': None
-    }
-    assert payload['filters'][0]['candidates'] == expected
-    assert len(payload['filters'][0]['errors']) == 1
+    expected = {"count": None, "sample": None}
+    assert payload["filters"][0]["candidates"] == expected
+    assert len(payload["filters"][0]["errors"]) == 1
 
 
 def test_saving_radio_sets_user(logged_in_client, factories):
-    artist = factories['music.Artist']()
-    f = {
-        'name': 'Test',
-        'config': [
-            {'type': 'artist', 'ids': [artist.pk]}
-        ]
-    }
-    url = reverse('api:v1:radios:radios-list')
+    artist = factories["music.Artist"]()
+    f = {"name": "Test", "config": [{"type": "artist", "ids": [artist.pk]}]}
+    url = reverse("api:v1:radios:radios-list")
     response = logged_in_client.post(
-        url,
-        json.dumps(f),
-        content_type="application/json")
+        url, json.dumps(f), content_type="application/json"
+    )
 
     assert response.status_code == 201
 
-    radio = logged_in_client.user.radios.latest('id')
-    assert radio.name == 'Test'
+    radio = logged_in_client.user.radios.latest("id")
+    assert radio.name == "Test"
     assert radio.user == logged_in_client.user
 
 
 def test_user_can_detail_his_radio(logged_in_client, factories):
-    radio = factories['radios.Radio'](user=logged_in_client.user)
-    url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk})
+    radio = factories["radios.Radio"](user=logged_in_client.user)
+    url = reverse("api:v1:radios:radios-detail", kwargs={"pk": radio.pk})
     response = logged_in_client.get(url)
 
     assert response.status_code == 200
 
 
 def test_user_can_detail_public_radio(logged_in_client, factories):
-    radio = factories['radios.Radio'](is_public=True)
-    url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk})
+    radio = factories["radios.Radio"](is_public=True)
+    url = reverse("api:v1:radios:radios-detail", kwargs={"pk": radio.pk})
     response = logged_in_client.get(url)
 
     assert response.status_code == 200
 
 
 def test_user_cannot_detail_someone_else_radio(logged_in_client, factories):
-    radio = factories['radios.Radio'](is_public=False)
-    url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk})
+    radio = factories["radios.Radio"](is_public=False)
+    url = reverse("api:v1:radios:radios-detail", kwargs={"pk": radio.pk})
     response = logged_in_client.get(url)
 
     assert response.status_code == 404
 
 
 def test_user_can_edit_his_radio(logged_in_client, factories):
-    radio = factories['radios.Radio'](user=logged_in_client.user)
-    url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk})
+    radio = factories["radios.Radio"](user=logged_in_client.user)
+    url = reverse("api:v1:radios:radios-detail", kwargs={"pk": radio.pk})
     response = logged_in_client.put(
-        url,
-        json.dumps({'name': 'new', 'config': []}),
-        content_type="application/json")
+        url, json.dumps({"name": "new", "config": []}), content_type="application/json"
+    )
 
     radio.refresh_from_db()
     assert response.status_code == 200
-    assert radio.name == 'new'
+    assert radio.name == "new"
 
 
 def test_user_cannot_edit_someone_else_radio(logged_in_client, factories):
-    radio = factories['radios.Radio']()
-    url = reverse('api:v1:radios:radios-detail', kwargs={'pk': radio.pk})
+    radio = factories["radios.Radio"]()
+    url = reverse("api:v1:radios:radios-detail", kwargs={"pk": radio.pk})
     response = logged_in_client.put(
-        url,
-        json.dumps({'name': 'new', 'config': []}),
-        content_type="application/json")
+        url, json.dumps({"name": "new", "config": []}), content_type="application/json"
+    )
 
     assert response.status_code == 404
 
 
 def test_clean_config_is_called_on_serializer_save(mocker, factories):
-    user = factories['users.User']()
-    artist = factories['music.Artist']()
-    data= {
-        'name': 'Test',
-        'config': [
-            {'type': 'artist', 'ids': [artist.pk]}
-        ]
-    }
-    spied = mocker.spy(filters.registry['artist'], 'clean_config')
+    user = factories["users.User"]()
+    artist = factories["music.Artist"]()
+    data = {"name": "Test", "config": [{"type": "artist", "ids": [artist.pk]}]}
+    spied = mocker.spy(filters.registry["artist"], "clean_config")
     serializer = serializers.RadioSerializer(data=data)
     assert serializer.is_valid()
     instance = serializer.save(user=user)
-    spied.assert_called_once_with(data['config'][0])
-    assert instance.config[0]['names'] == [artist.name]
+    spied.assert_called_once_with(data["config"][0])
+    assert instance.config[0]["names"] == [artist.name]
diff --git a/api/tests/radios/test_filters.py b/api/tests/radios/test_filters.py
index 27166b4ab2..025fc7a636 100644
--- a/api/tests/radios/test_filters.py
+++ b/api/tests/radios/test_filters.py
@@ -8,154 +8,147 @@ from funkwhale_api.radios import filters
 
 @filters.registry.register
 class NoopFilter(filters.RadioFilter):
-    code = 'noop'
+    code = "noop"
+
     def get_query(self, candidates, **kwargs):
         return
 
 
 def test_most_simple_radio_does_not_filter_anything(factories):
-    tracks = factories['music.Track'].create_batch(3)
-    radio = factories['radios.Radio'](config=[{'type': 'noop'}])
+    tracks = factories["music.Track"].create_batch(3)
+    radio = factories["radios.Radio"](config=[{"type": "noop"}])
 
     assert radio.version == 0
     assert radio.get_candidates().count() == 3
 
 
-
 def test_filter_can_use_custom_queryset(factories):
-    tracks = factories['music.Track'].create_batch(3)
+    tracks = factories["music.Track"].create_batch(3)
     candidates = Track.objects.filter(pk=tracks[0].pk)
 
-    qs = filters.run([{'type': 'noop'}], candidates=candidates)
+    qs = filters.run([{"type": "noop"}], candidates=candidates)
     assert qs.count() == 1
     assert qs.first() == tracks[0]
 
 
 def test_filter_on_tag(factories):
-    tracks = factories['music.Track'].create_batch(3, tags=['metal'])
-    factories['music.Track'].create_batch(3, tags=['pop'])
+    tracks = factories["music.Track"].create_batch(3, tags=["metal"])
+    factories["music.Track"].create_batch(3, tags=["pop"])
     expected = tracks
-    f = [
-        {'type': 'tag', 'names': ['metal']}
-    ]
+    f = [{"type": "tag", "names": ["metal"]}]
 
     candidates = filters.run(f)
-    assert list(candidates.order_by('pk')) == expected
+    assert list(candidates.order_by("pk")) == expected
 
 
 def test_filter_on_artist(factories):
-    artist1 = factories['music.Artist']()
-    artist2 = factories['music.Artist']()
-    factories['music.Track'].create_batch(3, artist=artist1)
-    factories['music.Track'].create_batch(3, artist=artist2)
-    expected = list(artist1.tracks.order_by('pk'))
-    f = [
-        {'type': 'artist', 'ids': [artist1.pk]}
-    ]
+    artist1 = factories["music.Artist"]()
+    artist2 = factories["music.Artist"]()
+    factories["music.Track"].create_batch(3, artist=artist1)
+    factories["music.Track"].create_batch(3, artist=artist2)
+    expected = list(artist1.tracks.order_by("pk"))
+    f = [{"type": "artist", "ids": [artist1.pk]}]
 
     candidates = filters.run(f)
-    assert list(candidates.order_by('pk')) == expected
+    assert list(candidates.order_by("pk")) == expected
 
 
 def test_can_combine_with_or(factories):
-    artist1 = factories['music.Artist']()
-    artist2 = factories['music.Artist']()
-    artist3 = factories['music.Artist']()
-    factories['music.Track'].create_batch(3, artist=artist1)
-    factories['music.Track'].create_batch(3, artist=artist2)
-    factories['music.Track'].create_batch(3, artist=artist3)
-    expected = Track.objects.exclude(artist=artist3).order_by('pk')
+    artist1 = factories["music.Artist"]()
+    artist2 = factories["music.Artist"]()
+    artist3 = factories["music.Artist"]()
+    factories["music.Track"].create_batch(3, artist=artist1)
+    factories["music.Track"].create_batch(3, artist=artist2)
+    factories["music.Track"].create_batch(3, artist=artist3)
+    expected = Track.objects.exclude(artist=artist3).order_by("pk")
     f = [
-        {'type': 'artist', 'ids': [artist1.pk]},
-        {'type': 'artist', 'ids': [artist2.pk], 'operator': 'or'},
+        {"type": "artist", "ids": [artist1.pk]},
+        {"type": "artist", "ids": [artist2.pk], "operator": "or"},
     ]
 
     candidates = filters.run(f)
-    assert list(candidates.order_by('pk')) == list(expected)
+    assert list(candidates.order_by("pk")) == list(expected)
 
 
 def test_can_combine_with_and(factories):
-    artist1 = factories['music.Artist']()
-    artist2 = factories['music.Artist']()
-    metal_tracks = factories['music.Track'].create_batch(
-        2, artist=artist1, tags=['metal'])
-    factories['music.Track'].create_batch(2, artist=artist1, tags=['pop'])
-    factories['music.Track'].create_batch(3, artist=artist2)
+    artist1 = factories["music.Artist"]()
+    artist2 = factories["music.Artist"]()
+    metal_tracks = factories["music.Track"].create_batch(
+        2, artist=artist1, tags=["metal"]
+    )
+    factories["music.Track"].create_batch(2, artist=artist1, tags=["pop"])
+    factories["music.Track"].create_batch(3, artist=artist2)
     expected = metal_tracks
     f = [
-        {'type': 'artist', 'ids': [artist1.pk]},
-        {'type': 'tag', 'names': ['metal'], 'operator': 'and'},
+        {"type": "artist", "ids": [artist1.pk]},
+        {"type": "tag", "names": ["metal"], "operator": "and"},
     ]
 
     candidates = filters.run(f)
-    assert list(candidates.order_by('pk')) == list(expected)
+    assert list(candidates.order_by("pk")) == list(expected)
 
 
 def test_can_negate(factories):
-    artist1 = factories['music.Artist']()
-    artist2 = factories['music.Artist']()
-    factories['music.Track'].create_batch(3, artist=artist1)
-    factories['music.Track'].create_batch(3, artist=artist2)
-    expected = artist2.tracks.order_by('pk')
-    f = [
-        {'type': 'artist', 'ids': [artist1.pk], 'not': True},
-    ]
+    artist1 = factories["music.Artist"]()
+    artist2 = factories["music.Artist"]()
+    factories["music.Track"].create_batch(3, artist=artist1)
+    factories["music.Track"].create_batch(3, artist=artist2)
+    expected = artist2.tracks.order_by("pk")
+    f = [{"type": "artist", "ids": [artist1.pk], "not": True}]
 
     candidates = filters.run(f)
-    assert list(candidates.order_by('pk')) == list(expected)
+    assert list(candidates.order_by("pk")) == list(expected)
 
 
 def test_can_group(factories):
-    artist1 = factories['music.Artist']()
-    artist2 = factories['music.Artist']()
-    factories['music.Track'].create_batch(2, artist=artist1)
-    t1 = factories['music.Track'].create_batch(
-        2, artist=artist1, tags=['metal'])
-    factories['music.Track'].create_batch(2, artist=artist2)
-    t2 = factories['music.Track'].create_batch(
-        2, artist=artist2, tags=['metal'])
-    factories['music.Track'].create_batch(2, tags=['metal'])
+    artist1 = factories["music.Artist"]()
+    artist2 = factories["music.Artist"]()
+    factories["music.Track"].create_batch(2, artist=artist1)
+    t1 = factories["music.Track"].create_batch(2, artist=artist1, tags=["metal"])
+    factories["music.Track"].create_batch(2, artist=artist2)
+    t2 = factories["music.Track"].create_batch(2, artist=artist2, tags=["metal"])
+    factories["music.Track"].create_batch(2, tags=["metal"])
     expected = t1 + t2
     f = [
-        {'type': 'tag', 'names': ['metal']},
-        {'type': 'group', 'operator': 'and', 'filters': [
-            {'type': 'artist', 'ids': [artist1.pk], 'operator': 'or'},
-            {'type': 'artist', 'ids': [artist2.pk], 'operator': 'or'},
-        ]}
+        {"type": "tag", "names": ["metal"]},
+        {
+            "type": "group",
+            "operator": "and",
+            "filters": [
+                {"type": "artist", "ids": [artist1.pk], "operator": "or"},
+                {"type": "artist", "ids": [artist2.pk], "operator": "or"},
+            ],
+        },
     ]
 
     candidates = filters.run(f)
-    assert list(candidates.order_by('pk')) == list(expected)
+    assert list(candidates.order_by("pk")) == list(expected)
 
 
 def test_artist_filter_clean_config(factories):
-    artist1 = factories['music.Artist']()
-    artist2 = factories['music.Artist']()
+    artist1 = factories["music.Artist"]()
+    artist2 = factories["music.Artist"]()
 
-    config = filters.clean_config(
-        {'type': 'artist', 'ids': [artist2.pk, artist1.pk]})
+    config = filters.clean_config({"type": "artist", "ids": [artist2.pk, artist1.pk]})
 
     expected = {
-        'type': 'artist',
-        'ids': [artist1.pk, artist2.pk],
-        'names': [artist1.name, artist2.name]
+        "type": "artist",
+        "ids": [artist1.pk, artist2.pk],
+        "names": [artist1.name, artist2.name],
     }
     assert filters.clean_config(config) == expected
 
 
 def test_can_check_artist_filter(factories):
-    artist = factories['music.Artist']()
+    artist = factories["music.Artist"]()
 
-    assert filters.validate({'type': 'artist', 'ids': [artist.pk]})
+    assert filters.validate({"type": "artist", "ids": [artist.pk]})
     with pytest.raises(ValidationError):
-        filters.validate({'type': 'artist', 'ids': [artist.pk + 1]})
+        filters.validate({"type": "artist", "ids": [artist.pk + 1]})
 
 
 def test_can_check_operator():
-    assert filters.validate(
-        {'type': 'group', 'operator': 'or', 'filters': []})
-    assert filters.validate(
-        {'type': 'group', 'operator': 'and', 'filters': []})
+    assert filters.validate({"type": "group", "operator": "or", "filters": []})
+    assert filters.validate({"type": "group", "operator": "and", "filters": []})
     with pytest.raises(ValidationError):
-        assert filters.validate(
-            {'type': 'group', 'operator': 'nope', 'filters': []})
+        assert filters.validate({"type": "group", "operator": "nope", "filters": []})
diff --git a/api/tests/radios/test_radios.py b/api/tests/radios/test_radios.py
index b166b648c5..ebc45f1f94 100644
--- a/api/tests/radios/test_radios.py
+++ b/api/tests/radios/test_radios.py
@@ -51,9 +51,9 @@ def test_can_pick_by_weight():
 
 
 def test_can_get_choices_for_favorites_radio(factories):
-    files = factories['music.TrackFile'].create_batch(10)
+    files = factories["music.TrackFile"].create_batch(10)
     tracks = [f.track for f in files]
-    user = factories['users.User']()
+    user = factories["users.User"]()
     for i in range(5):
         TrackFavorite.add(track=random.choice(tracks), user=user)
 
@@ -71,56 +71,52 @@ def test_can_get_choices_for_favorites_radio(factories):
 
 
 def test_can_get_choices_for_custom_radio(factories):
-    artist = factories['music.Artist']()
-    files = factories['music.TrackFile'].create_batch(
-        5, track__artist=artist)
+    artist = factories["music.Artist"]()
+    files = factories["music.TrackFile"].create_batch(5, track__artist=artist)
     tracks = [f.track for f in files]
-    wrong_files = factories['music.TrackFile'].create_batch(5)
+    wrong_files = factories["music.TrackFile"].create_batch(5)
     wrong_tracks = [f.track for f in wrong_files]
 
-    session = factories['radios.CustomRadioSession'](
-        custom_radio__config=[{'type': 'artist', 'ids': [artist.pk]}]
+    session = factories["radios.CustomRadioSession"](
+        custom_radio__config=[{"type": "artist", "ids": [artist.pk]}]
     )
     choices = session.radio.get_choices()
 
     expected = [t.pk for t in tracks]
-    assert list(choices.values_list('id', flat=True)) == expected
+    assert list(choices.values_list("id", flat=True)) == expected
 
 
 def test_cannot_start_custom_radio_if_not_owner_or_not_public(factories):
-    user = factories['users.User']()
-    artist = factories['music.Artist']()
-    radio = factories['radios.Radio'](
-        config=[{'type': 'artist', 'ids': [artist.pk]}]
-    )
+    user = factories["users.User"]()
+    artist = factories["music.Artist"]()
+    radio = factories["radios.Radio"](config=[{"type": "artist", "ids": [artist.pk]}])
     serializer = serializers.RadioSessionSerializer(
-        data={
-            'radio_type': 'custom', 'custom_radio': radio.pk, 'user': user.pk}
+        data={"radio_type": "custom", "custom_radio": radio.pk, "user": user.pk}
     )
     message = "You don't have access to this radio"
     assert not serializer.is_valid()
-    assert message in serializer.errors['non_field_errors']
+    assert message in serializer.errors["non_field_errors"]
 
 
 def test_can_start_custom_radio_from_api(logged_in_client, factories):
-    artist = factories['music.Artist']()
-    radio = factories['radios.Radio'](
-        config=[{'type': 'artist', 'ids': [artist.pk]}],
-        user=logged_in_client.user
+    artist = factories["music.Artist"]()
+    radio = factories["radios.Radio"](
+        config=[{"type": "artist", "ids": [artist.pk]}], user=logged_in_client.user
     )
-    url = reverse('api:v1:radios:sessions-list')
+    url = reverse("api:v1:radios:sessions-list")
     response = logged_in_client.post(
-        url, {'radio_type': 'custom', 'custom_radio': radio.pk})
+        url, {"radio_type": "custom", "custom_radio": radio.pk}
+    )
     assert response.status_code == 201
-    session = radio.sessions.latest('id')
-    assert session.radio_type == 'custom'
+    session = radio.sessions.latest("id")
+    assert session.radio_type == "custom"
     assert session.user == logged_in_client.user
 
 
 def test_can_use_radio_session_to_filter_choices(factories):
-    files = factories['music.TrackFile'].create_batch(30)
+    files = factories["music.TrackFile"].create_batch(30)
     tracks = [f.track for f in files]
-    user = factories['users.User']()
+    user = factories["users.User"]()
     radio = radios.RandomRadio()
     session = radio.start_session(user)
 
@@ -129,13 +125,13 @@ def test_can_use_radio_session_to_filter_choices(factories):
 
     # ensure 30 differents tracks have been suggested
     tracks_id = [
-        session_track.track.pk
-        for session_track in session.session_tracks.all()]
+        session_track.track.pk for session_track in session.session_tracks.all()
+    ]
     assert len(set(tracks_id)) == 30
 
 
 def test_can_restore_radio_from_previous_session(factories):
-    user = factories['users.User']()
+    user = factories["users.User"]()
     radio = radios.RandomRadio()
     session = radio.start_session(user)
 
@@ -144,37 +140,37 @@ def test_can_restore_radio_from_previous_session(factories):
 
 
 def test_can_start_radio_for_logged_in_user(logged_in_client):
-    url = reverse('api:v1:radios:sessions-list')
-    response = logged_in_client.post(url, {'radio_type': 'random'})
-    session = models.RadioSession.objects.latest('id')
-    assert session.radio_type == 'random'
+    url = reverse("api:v1:radios:sessions-list")
+    response = logged_in_client.post(url, {"radio_type": "random"})
+    session = models.RadioSession.objects.latest("id")
+    assert session.radio_type == "random"
     assert session.user == logged_in_client.user
 
 
 def test_can_get_track_for_session_from_api(factories, logged_in_client):
-    files = factories['music.TrackFile'].create_batch(1)
+    files = factories["music.TrackFile"].create_batch(1)
     tracks = [f.track for f in files]
-    url = reverse('api:v1:radios:sessions-list')
-    response = logged_in_client.post(url, {'radio_type': 'random'})
-    session = models.RadioSession.objects.latest('id')
+    url = reverse("api:v1:radios:sessions-list")
+    response = logged_in_client.post(url, {"radio_type": "random"})
+    session = models.RadioSession.objects.latest("id")
 
-    url = reverse('api:v1:radios:tracks-list')
-    response = logged_in_client.post(url, {'session': session.pk})
-    data = json.loads(response.content.decode('utf-8'))
+    url = reverse("api:v1:radios:tracks-list")
+    response = logged_in_client.post(url, {"session": session.pk})
+    data = json.loads(response.content.decode("utf-8"))
 
-    assert data['track']['id'] == tracks[0].id
-    assert data['position'] == 1
+    assert data["track"]["id"] == tracks[0].id
+    assert data["position"] == 1
 
-    next_track = factories['music.TrackFile']().track
-    response = logged_in_client.post(url, {'session': session.pk})
-    data = json.loads(response.content.decode('utf-8'))
+    next_track = factories["music.TrackFile"]().track
+    response = logged_in_client.post(url, {"session": session.pk})
+    data = json.loads(response.content.decode("utf-8"))
 
-    assert data['track']['id'] == next_track.id
-    assert data['position'] == 2
+    assert data["track"]["id"] == next_track.id
+    assert data["position"] == 2
 
 
 def test_related_object_radio_validate_related_object(factories):
-    user = factories['users.User']()
+    user = factories["users.User"]()
     # cannot start without related object
     radio = radios.ArtistRadio()
     with pytest.raises(ValidationError):
@@ -187,59 +183,57 @@ def test_related_object_radio_validate_related_object(factories):
 
 
 def test_can_start_artist_radio(factories):
-    user = factories['users.User']()
-    artist = factories['music.Artist']()
-    wrong_files = factories['music.TrackFile'].create_batch(5)
+    user = factories["users.User"]()
+    artist = factories["music.Artist"]()
+    wrong_files = factories["music.TrackFile"].create_batch(5)
     wrong_tracks = [f.track for f in wrong_files]
-    good_files = factories['music.TrackFile'].create_batch(
-        5, track__artist=artist)
+    good_files = factories["music.TrackFile"].create_batch(5, track__artist=artist)
     good_tracks = [f.track for f in good_files]
 
     radio = radios.ArtistRadio()
     session = radio.start_session(user, related_object=artist)
-    assert session.radio_type == 'artist'
+    assert session.radio_type == "artist"
     for i in range(5):
         assert radio.pick() in good_tracks
 
 
 def test_can_start_tag_radio(factories):
-    user = factories['users.User']()
-    tag = factories['taggit.Tag']()
-    wrong_files = factories['music.TrackFile'].create_batch(5)
+    user = factories["users.User"]()
+    tag = factories["taggit.Tag"]()
+    wrong_files = factories["music.TrackFile"].create_batch(5)
     wrong_tracks = [f.track for f in wrong_files]
-    good_files = factories['music.TrackFile'].create_batch(
-        5, track__tags=[tag])
+    good_files = factories["music.TrackFile"].create_batch(5, track__tags=[tag])
     good_tracks = [f.track for f in good_files]
 
     radio = radios.TagRadio()
     session = radio.start_session(user, related_object=tag)
-    assert session.radio_type == 'tag'
+    assert session.radio_type == "tag"
     for i in range(5):
         assert radio.pick() in good_tracks
 
 
-def test_can_start_artist_radio_from_api(
-        logged_in_api_client, preferences, factories):
-    artist = factories['music.Artist']()
-    url = reverse('api:v1:radios:sessions-list')
+def test_can_start_artist_radio_from_api(logged_in_api_client, preferences, factories):
+    artist = factories["music.Artist"]()
+    url = reverse("api:v1:radios:sessions-list")
 
     response = logged_in_api_client.post(
-        url, {'radio_type': 'artist', 'related_object_id': artist.id})
+        url, {"radio_type": "artist", "related_object_id": artist.id}
+    )
 
     assert response.status_code == 201
 
-    session = models.RadioSession.objects.latest('id')
+    session = models.RadioSession.objects.latest("id")
 
-    assert session.radio_type == 'artist'
+    assert session.radio_type == "artist"
     assert session.related_object == artist
 
 
 def test_can_start_less_listened_radio(factories):
-    user = factories['users.User']()
-    wrong_files = factories['music.TrackFile'].create_batch(5)
+    user = factories["users.User"]()
+    wrong_files = factories["music.TrackFile"].create_batch(5)
     for f in wrong_files:
-        factories['history.Listening'](track=f.track, user=user)
-    good_files = factories['music.TrackFile'].create_batch(5)
+        factories["history.Listening"](track=f.track, user=user)
+    good_files = factories["music.TrackFile"].create_batch(5)
     good_tracks = [f.track for f in good_files]
     radio = radios.LessListenedRadio()
     session = radio.start_session(user)
diff --git a/api/tests/requests/test_models.py b/api/tests/requests/test_models.py
index 797656bd70..d6b71635af 100644
--- a/api/tests/requests/test_models.py
+++ b/api/tests/requests/test_models.py
@@ -4,20 +4,20 @@ from django.forms import ValidationError
 
 
 def test_can_bind_import_batch_to_request(factories):
-    request = factories['requests.ImportRequest']()
+    request = factories["requests.ImportRequest"]()
 
-    assert request.status == 'pending'
+    assert request.status == "pending"
 
     # when we create the import, we consider the request as accepted
-    batch = factories['music.ImportBatch'](import_request=request)
+    batch = factories["music.ImportBatch"](import_request=request)
     request.refresh_from_db()
 
-    assert request.status == 'accepted'
+    assert request.status == "accepted"
 
     # now, the batch is finished, therefore the request status should be
     # imported
-    batch.status = 'finished'
-    batch.save(update_fields=['status'])
+    batch.status = "finished"
+    batch.save(update_fields=["status"])
     request.refresh_from_db()
 
-    assert request.status == 'imported'
+    assert request.status == "imported"
diff --git a/api/tests/requests/test_views.py b/api/tests/requests/test_views.py
index 6c34f9ad19..0d64336720 100644
--- a/api/tests/requests/test_views.py
+++ b/api/tests/requests/test_views.py
@@ -2,25 +2,25 @@ from django.urls import reverse
 
 
 def test_request_viewset_requires_auth(db, api_client):
-    url = reverse('api:v1:requests:import-requests-list')
+    url = reverse("api:v1:requests:import-requests-list")
     response = api_client.get(url)
     assert response.status_code == 401
 
 
 def test_user_can_create_request(logged_in_api_client):
-    url = reverse('api:v1:requests:import-requests-list')
+    url = reverse("api:v1:requests:import-requests-list")
     user = logged_in_api_client.user
     data = {
-        'artist_name': 'System of a Down',
-        'albums': 'All please!',
-        'comment': 'Please, they rock!',
+        "artist_name": "System of a Down",
+        "albums": "All please!",
+        "comment": "Please, they rock!",
     }
     response = logged_in_api_client.post(url, data)
 
     assert response.status_code == 201
 
-    ir = user.import_requests.latest('id')
-    assert ir.status == 'pending'
+    ir = user.import_requests.latest("id")
+    assert ir.status == "pending"
     assert ir.creation_date is not None
     for field, value in data.items():
         assert getattr(ir, field) == value
diff --git a/api/tests/subsonic/test_authentication.py b/api/tests/subsonic/test_authentication.py
index 656f8c44d8..f404c8b0ab 100644
--- a/api/tests/subsonic/test_authentication.py
+++ b/api/tests/subsonic/test_authentication.py
@@ -7,16 +7,12 @@ from funkwhale_api.subsonic import authentication
 
 
 def test_auth_with_salt(api_request, factories):
-    salt = 'salt'
-    user = factories['users.User']()
-    user.subsonic_api_token = 'password'
+    salt = "salt"
+    user = factories["users.User"]()
+    user.subsonic_api_token = "password"
     user.save()
-    token = authentication.get_token(salt, 'password')
-    request = api_request.get('/', {
-        't': token,
-        's': salt,
-        'u': user.username
-    })
+    token = authentication.get_token(salt, "password")
+    request = api_request.get("/", {"t": token, "s": salt, "u": user.username})
 
     authenticator = authentication.SubsonicAuthentication()
     u, _ = authenticator.authenticate(request)
@@ -25,16 +21,22 @@ def test_auth_with_salt(api_request, factories):
 
 
 def test_auth_with_password_hex(api_request, factories):
-    salt = 'salt'
-    user = factories['users.User']()
-    user.subsonic_api_token = 'password'
+    salt = "salt"
+    user = factories["users.User"]()
+    user.subsonic_api_token = "password"
     user.save()
-    token = authentication.get_token(salt, 'password')
-    request = api_request.get('/', {
-        'u': user.username,
-        'p': 'enc:{}'.format(binascii.hexlify(
-            user.subsonic_api_token.encode('utf-8')).decode('utf-8'))
-    })
+    token = authentication.get_token(salt, "password")
+    request = api_request.get(
+        "/",
+        {
+            "u": user.username,
+            "p": "enc:{}".format(
+                binascii.hexlify(user.subsonic_api_token.encode("utf-8")).decode(
+                    "utf-8"
+                )
+            ),
+        },
+    )
 
     authenticator = authentication.SubsonicAuthentication()
     u, _ = authenticator.authenticate(request)
@@ -43,15 +45,12 @@ def test_auth_with_password_hex(api_request, factories):
 
 
 def test_auth_with_password_cleartext(api_request, factories):
-    salt = 'salt'
-    user = factories['users.User']()
-    user.subsonic_api_token = 'password'
+    salt = "salt"
+    user = factories["users.User"]()
+    user.subsonic_api_token = "password"
     user.save()
-    token = authentication.get_token(salt, 'password')
-    request = api_request.get('/', {
-        'u': user.username,
-        'p': 'password',
-    })
+    token = authentication.get_token(salt, "password")
+    request = api_request.get("/", {"u": user.username, "p": "password"})
 
     authenticator = authentication.SubsonicAuthentication()
     u, _ = authenticator.authenticate(request)
@@ -60,15 +59,12 @@ def test_auth_with_password_cleartext(api_request, factories):
 
 
 def test_auth_with_inactive_users(api_request, factories):
-    salt = 'salt'
-    user = factories['users.User'](is_active=False)
-    user.subsonic_api_token = 'password'
+    salt = "salt"
+    user = factories["users.User"](is_active=False)
+    user.subsonic_api_token = "password"
     user.save()
-    token = authentication.get_token(salt, 'password')
-    request = api_request.get('/', {
-        'u': user.username,
-        'p': 'password',
-    })
+    token = authentication.get_token(salt, "password")
+    request = api_request.get("/", {"u": user.username, "p": "password"})
 
     authenticator = authentication.SubsonicAuthentication()
     with pytest.raises(exceptions.AuthenticationFailed):
diff --git a/api/tests/subsonic/test_renderers.py b/api/tests/subsonic/test_renderers.py
index 8e2ea3f85a..65dae6f229 100644
--- a/api/tests/subsonic/test_renderers.py
+++ b/api/tests/subsonic/test_renderers.py
@@ -5,37 +5,25 @@ from funkwhale_api.subsonic import renderers
 
 
 def test_json_renderer():
-    data = {'hello': 'world'}
+    data = {"hello": "world"}
     expected = {
-       'subsonic-response': {
-          'status': 'ok',
-          'version': '1.16.0',
-          'hello': 'world'
-       }
+        "subsonic-response": {"status": "ok", "version": "1.16.0", "hello": "world"}
     }
     renderer = renderers.SubsonicJSONRenderer()
     assert json.loads(renderer.render(data)) == expected
 
 
 def test_xml_renderer_dict_to_xml():
-    payload = {
-        'hello': 'world',
-        'item': [
-            {'this': 1},
-            {'some': 'node'},
-        ]
-    }
+    payload = {"hello": "world", "item": [{"this": 1}, {"some": "node"}]}
     expected = """<?xml version="1.0" encoding="UTF-8"?>
 <key hello="world"><item this="1" /><item some="node" /></key>"""
-    result = renderers.dict_to_xml_tree('key', payload)
+    result = renderers.dict_to_xml_tree("key", payload)
     exp = ET.fromstring(expected)
     assert ET.tostring(result) == ET.tostring(exp)
 
 
 def test_xml_renderer():
-    payload = {
-        'hello': 'world',
-    }
+    payload = {"hello": "world"}
     expected = b'<?xml version="1.0" encoding="UTF-8"?>\n<subsonic-response hello="world" status="ok" version="1.16.0" xmlns="http://subsonic.org/restapi" />'
 
     renderer = renderers.SubsonicXMLRenderer()
diff --git a/api/tests/subsonic/test_serializers.py b/api/tests/subsonic/test_serializers.py
index 6b9ec232da..6fdf02e2d1 100644
--- a/api/tests/subsonic/test_serializers.py
+++ b/api/tests/subsonic/test_serializers.py
@@ -3,136 +3,121 @@ from funkwhale_api.subsonic import serializers
 
 
 def test_get_artists_serializer(factories):
-    artist1 = factories['music.Artist'](name='eliot')
-    artist2 = factories['music.Artist'](name='Ellena')
-    artist3 = factories['music.Artist'](name='Rilay')
+    artist1 = factories["music.Artist"](name="eliot")
+    artist2 = factories["music.Artist"](name="Ellena")
+    artist3 = factories["music.Artist"](name="Rilay")
 
-    factories['music.Album'].create_batch(size=3, artist=artist1)
-    factories['music.Album'].create_batch(size=2, artist=artist2)
+    factories["music.Album"].create_batch(size=3, artist=artist1)
+    factories["music.Album"].create_batch(size=2, artist=artist2)
 
     expected = {
-        'ignoredArticles': '',
-        'index': [
+        "ignoredArticles": "",
+        "index": [
             {
-                'name': 'E',
-                'artist': [
-                    {
-                        'id': artist1.pk,
-                        'name': artist1.name,
-                        'albumCount': 3,
-                    },
-                    {
-                        'id': artist2.pk,
-                        'name': artist2.name,
-                        'albumCount': 2,
-                    },
-                ]
+                "name": "E",
+                "artist": [
+                    {"id": artist1.pk, "name": artist1.name, "albumCount": 3},
+                    {"id": artist2.pk, "name": artist2.name, "albumCount": 2},
+                ],
             },
             {
-                'name': 'R',
-                'artist': [
-                    {
-                        'id': artist3.pk,
-                        'name': artist3.name,
-                        'albumCount': 0,
-                    },
-                ]
+                "name": "R",
+                "artist": [{"id": artist3.pk, "name": artist3.name, "albumCount": 0}],
             },
-        ]
+        ],
     }
 
-    queryset = artist1.__class__.objects.filter(pk__in=[
-        artist1.pk, artist2.pk, artist3.pk
-    ])
+    queryset = artist1.__class__.objects.filter(
+        pk__in=[artist1.pk, artist2.pk, artist3.pk]
+    )
 
     assert serializers.GetArtistsSerializer(queryset).data == expected
 
 
 def test_get_artist_serializer(factories):
-    artist = factories['music.Artist']()
-    album = factories['music.Album'](artist=artist)
-    tracks = factories['music.Track'].create_batch(size=3, album=album)
+    artist = factories["music.Artist"]()
+    album = factories["music.Album"](artist=artist)
+    tracks = factories["music.Track"].create_batch(size=3, album=album)
 
     expected = {
-        'id': artist.pk,
-        'name': artist.name,
-        'albumCount': 1,
-        'album': [
+        "id": artist.pk,
+        "name": artist.name,
+        "albumCount": 1,
+        "album": [
             {
-                'id': album.pk,
-                'coverArt': 'al-{}'.format(album.id),
-                'artistId': artist.pk,
-                'name': album.title,
-                'artist': artist.name,
-                'songCount': len(tracks),
-                'created': album.creation_date,
-                'year': album.release_date.year,
+                "id": album.pk,
+                "coverArt": "al-{}".format(album.id),
+                "artistId": artist.pk,
+                "name": album.title,
+                "artist": artist.name,
+                "songCount": len(tracks),
+                "created": album.creation_date,
+                "year": album.release_date.year,
             }
-        ]
+        ],
     }
 
     assert serializers.GetArtistSerializer(artist).data == expected
 
 
 def test_get_album_serializer(factories):
-    artist = factories['music.Artist']()
-    album = factories['music.Album'](artist=artist)
-    track = factories['music.Track'](album=album)
-    tf = factories['music.TrackFile'](
-        track=track, bitrate=42000, duration=43, size=44)
+    artist = factories["music.Artist"]()
+    album = factories["music.Album"](artist=artist)
+    track = factories["music.Track"](album=album)
+    tf = factories["music.TrackFile"](track=track, bitrate=42000, duration=43, size=44)
 
     expected = {
-        'id': album.pk,
-        'artistId': artist.pk,
-        'name': album.title,
-        'artist': artist.name,
-        'songCount': 1,
-        'created': album.creation_date,
-        'year': album.release_date.year,
-        'coverArt': 'al-{}'.format(album.id),
-        'song': [
+        "id": album.pk,
+        "artistId": artist.pk,
+        "name": album.title,
+        "artist": artist.name,
+        "songCount": 1,
+        "created": album.creation_date,
+        "year": album.release_date.year,
+        "coverArt": "al-{}".format(album.id),
+        "song": [
             {
-                'id': track.pk,
-                'isDir': 'false',
-                'title': track.title,
-                'coverArt': 'al-{}'.format(album.id),
-                'album': album.title,
-                'artist': artist.name,
-                'track': track.position,
-                'year': track.album.release_date.year,
-                'contentType': tf.mimetype,
-                'suffix': tf.extension or '',
-                'bitrate': 42,
-                'duration': 43,
-                'size': 44,
-                'created': track.creation_date,
-                'albumId': album.pk,
-                'artistId': artist.pk,
-                'type': 'music',
+                "id": track.pk,
+                "isDir": "false",
+                "title": track.title,
+                "coverArt": "al-{}".format(album.id),
+                "album": album.title,
+                "artist": artist.name,
+                "track": track.position,
+                "year": track.album.release_date.year,
+                "contentType": tf.mimetype,
+                "suffix": tf.extension or "",
+                "bitrate": 42,
+                "duration": 43,
+                "size": 44,
+                "created": track.creation_date,
+                "albumId": album.pk,
+                "artistId": artist.pk,
+                "type": "music",
             }
-        ]
+        ],
     }
 
     assert serializers.GetAlbumSerializer(album).data == expected
 
 
 def test_starred_tracks2_serializer(factories):
-    artist = factories['music.Artist']()
-    album = factories['music.Album'](artist=artist)
-    track = factories['music.Track'](album=album)
-    tf = factories['music.TrackFile'](track=track)
-    favorite = factories['favorites.TrackFavorite'](track=track)
+    artist = factories["music.Artist"]()
+    album = factories["music.Album"](artist=artist)
+    track = factories["music.Track"](album=album)
+    tf = factories["music.TrackFile"](track=track)
+    favorite = factories["favorites.TrackFavorite"](track=track)
     expected = [serializers.get_track_data(album, track, tf)]
-    expected[0]['starred'] = favorite.creation_date
+    expected[0]["starred"] = favorite.creation_date
     data = serializers.get_starred_tracks_data([favorite])
     assert data == expected
 
 
 def test_get_album_list2_serializer(factories):
-    album1 = factories['music.Album']()
-    album2 = factories['music.Album']()
+    album1 = factories["music.Album"]()
+    album2 = factories["music.Album"]()
 
-    qs = music_models.Album.objects.with_tracks_count().order_by('pk')
+    qs = music_models.Album.objects.with_tracks_count().order_by("pk")
     expected = [
         serializers.get_album2_data(album1),
         serializers.get_album2_data(album2),
@@ -142,17 +127,17 @@ def test_get_album_list2_serializer(factories):
 
 
 def test_playlist_serializer(factories):
-    plt = factories['playlists.PlaylistTrack']()
+    plt = factories["playlists.PlaylistTrack"]()
     playlist = plt.playlist
-    qs = music_models.Album.objects.with_tracks_count().order_by('pk')
+    qs = music_models.Album.objects.with_tracks_count().order_by("pk")
     expected = {
-        'id': playlist.pk,
-        'name': playlist.name,
-        'owner': playlist.user.username,
-        'public': 'false',
-        'songCount': 1,
-        'duration': 0,
-        'created': playlist.creation_date,
+        "id": playlist.pk,
+        "name": playlist.name,
+        "owner": playlist.user.username,
+        "public": "false",
+        "songCount": 1,
+        "duration": 0,
+        "created": playlist.creation_date,
     }
     qs = playlist.__class__.objects.with_tracks_count()
     data = serializers.get_playlist_data(qs.first())
@@ -160,21 +145,19 @@ def test_playlist_serializer(factories):
 
 
 def test_playlist_detail_serializer(factories):
-    plt = factories['playlists.PlaylistTrack']()
-    tf = factories['music.TrackFile'](track=plt.track)
+    plt = factories["playlists.PlaylistTrack"]()
+    tf = factories["music.TrackFile"](track=plt.track)
     playlist = plt.playlist
-    qs = music_models.Album.objects.with_tracks_count().order_by('pk')
+    qs = music_models.Album.objects.with_tracks_count().order_by("pk")
     expected = {
-        'id': playlist.pk,
-        'name': playlist.name,
-        'owner': playlist.user.username,
-        'public': 'false',
-        'songCount': 1,
-        'duration': 0,
-        'created': playlist.creation_date,
-        'entry': [
-            serializers.get_track_data(plt.track.album, plt.track, tf)
-        ]
+        "id": playlist.pk,
+        "name": playlist.name,
+        "owner": playlist.user.username,
+        "public": "false",
+        "songCount": 1,
+        "duration": 0,
+        "created": playlist.creation_date,
+        "entry": [serializers.get_track_data(plt.track.album, plt.track, tf)],
     }
     qs = playlist.__class__.objects.with_tracks_count()
     data = serializers.get_playlist_detail_data(qs.first())
@@ -182,50 +165,47 @@ def test_playlist_detail_serializer(factories):
 
 
 def test_directory_serializer_artist(factories):
-    track = factories['music.Track']()
-    tf = factories['music.TrackFile'](
-        track=track, bitrate=42000, duration=43, size=44)
+    track = factories["music.Track"]()
+    tf = factories["music.TrackFile"](track=track, bitrate=42000, duration=43, size=44)
     album = track.album
     artist = track.artist
 
     expected = {
-        'id': artist.pk,
-        'parent': 1,
-        'name': artist.name,
-        'child': [{
-            'id': track.pk,
-            'isDir': 'false',
-            'title': track.title,
-            'album': album.title,
-            'artist': artist.name,
-            'track': track.position,
-            'year': track.album.release_date.year,
-            'contentType': tf.mimetype,
-            'suffix': tf.extension or '',
-            'bitrate': 42,
-            'duration': 43,
-            'size': 44,
-            'created': track.creation_date,
-            'albumId': album.pk,
-            'artistId': artist.pk,
-            'parent': artist.pk,
-            'type': 'music',
-        }]
+        "id": artist.pk,
+        "parent": 1,
+        "name": artist.name,
+        "child": [
+            {
+                "id": track.pk,
+                "isDir": "false",
+                "title": track.title,
+                "album": album.title,
+                "artist": artist.name,
+                "track": track.position,
+                "year": track.album.release_date.year,
+                "contentType": tf.mimetype,
+                "suffix": tf.extension or "",
+                "bitrate": 42,
+                "duration": 43,
+                "size": 44,
+                "created": track.creation_date,
+                "albumId": album.pk,
+                "artistId": artist.pk,
+                "parent": artist.pk,
+                "type": "music",
+            }
+        ],
     }
     data = serializers.get_music_directory_data(artist)
     assert data == expected
 
 
 def test_scrobble_serializer(factories):
-    tf = factories['music.TrackFile']()
+    tf = factories["music.TrackFile"]()
     track = tf.track
-    user = factories['users.User']()
-    payload = {
-        'id': track.pk,
-        'submission': True,
-    }
-    serializer = serializers.ScrobbleSerializer(
-        data=payload, context={'user': user})
+    user = factories["users.User"]()
+    payload = {"id": track.pk, "submission": True}
+    serializer = serializers.ScrobbleSerializer(data=payload, context={"user": user})
 
     assert serializer.is_valid(raise_exception=True)
 
diff --git a/api/tests/subsonic/test_views.py b/api/tests/subsonic/test_views.py
index 52e410e52b..d06bf5525d 100644
--- a/api/tests/subsonic/test_views.py
+++ b/api/tests/subsonic/test_views.py
@@ -18,372 +18,341 @@ def render_json(data):
 
 
 def test_render_content_json(db, api_client):
-    url = reverse('api:subsonic-ping')
-    response = api_client.get(url, {'f': 'json'})
+    url = reverse("api:subsonic-ping")
+    response = api_client.get(url, {"f": "json"})
 
-    expected = {
-        'status': 'ok',
-        'version': '1.16.0'
-    }
+    expected = {"status": "ok", "version": "1.16.0"}
     assert response.status_code == 200
     assert json.loads(response.content) == render_json(expected)
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_exception_wrong_credentials(f, db, api_client):
-    url = reverse('api:subsonic-ping')
-    response = api_client.get(url, {'f': f, 'u': 'yolo'})
+    url = reverse("api:subsonic-ping")
+    response = api_client.get(url, {"f": f, "u": "yolo"})
 
     expected = {
-        'status': 'failed',
-        'error': {
-            'code': 40,
-            'message': 'Wrong username or password.'
-        }
+        "status": "failed",
+        "error": {"code": 40, "message": "Wrong username or password."},
     }
     assert response.status_code == 200
     assert response.data == expected
 
 
 def test_disabled_subsonic(preferences, api_client):
-    preferences['subsonic__enabled'] = False
-    url = reverse('api:subsonic-ping')
+    preferences["subsonic__enabled"] = False
+    url = reverse("api:subsonic-ping")
     response = api_client.get(url)
     assert response.status_code == 405
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_get_license(f, db, logged_in_api_client, mocker):
-    url = reverse('api:subsonic-get-license')
-    assert url.endswith('getLicense') is True
+    url = reverse("api:subsonic-get-license")
+    assert url.endswith("getLicense") is True
     now = timezone.now()
-    mocker.patch('django.utils.timezone.now', return_value=now)
-    response = logged_in_api_client.get(url, {'f': f})
+    mocker.patch("django.utils.timezone.now", return_value=now)
+    response = logged_in_api_client.get(url, {"f": f})
     expected = {
-        'status': 'ok',
-        'version': '1.16.0',
-        'license': {
-            'valid': 'true',
-            'email': 'valid@valid.license',
-            'licenseExpires': now + datetime.timedelta(days=365)
-        }
+        "status": "ok",
+        "version": "1.16.0",
+        "license": {
+            "valid": "true",
+            "email": "valid@valid.license",
+            "licenseExpires": now + datetime.timedelta(days=365),
+        },
     }
     assert response.status_code == 200
     assert response.data == expected
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_ping(f, db, api_client):
-    url = reverse('api:subsonic-ping')
-    response = api_client.get(url, {'f': f})
+    url = reverse("api:subsonic-ping")
+    response = api_client.get(url, {"f": f})
 
-    expected = {
-        'status': 'ok',
-        'version': '1.16.0',
-    }
+    expected = {"status": "ok", "version": "1.16.0"}
     assert response.status_code == 200
     assert response.data == expected
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_get_artists(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-get-artists')
-    assert url.endswith('getArtists') is True
-    artists = factories['music.Artist'].create_batch(size=10)
+    url = reverse("api:subsonic-get-artists")
+    assert url.endswith("getArtists") is True
+    artists = factories["music.Artist"].create_batch(size=10)
     expected = {
-        'artists': serializers.GetArtistsSerializer(
+        "artists": serializers.GetArtistsSerializer(
             music_models.Artist.objects.all()
         ).data
     }
-    response = logged_in_api_client.get(url, {'f': f})
+    response = logged_in_api_client.get(url, {"f": f})
 
     assert response.status_code == 200
     assert response.data == expected
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_get_artist(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-get-artist')
-    assert url.endswith('getArtist') is True
-    artist = factories['music.Artist']()
-    albums = factories['music.Album'].create_batch(size=3, artist=artist)
-    expected = {
-        'artist': serializers.GetArtistSerializer(artist).data
-    }
-    response = logged_in_api_client.get(url, {'id': artist.pk})
+    url = reverse("api:subsonic-get-artist")
+    assert url.endswith("getArtist") is True
+    artist = factories["music.Artist"]()
+    albums = factories["music.Album"].create_batch(size=3, artist=artist)
+    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
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_get_artist_info2(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-get-artist-info2')
-    assert url.endswith('getArtistInfo2') is True
-    artist = factories['music.Artist']()
+    url = reverse("api:subsonic-get-artist-info2")
+    assert url.endswith("getArtistInfo2") is True
+    artist = factories["music.Artist"]()
 
-    expected = {
-        'artist-info2': {}
-    }
-    response = logged_in_api_client.get(url, {'id': artist.pk})
+    expected = {"artist-info2": {}}
+    response = logged_in_api_client.get(url, {"id": artist.pk})
 
     assert response.status_code == 200
     assert response.data == expected
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_get_album(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-get-album')
-    assert url.endswith('getAlbum') is True
-    artist = factories['music.Artist']()
-    album = factories['music.Album'](artist=artist)
-    tracks = factories['music.Track'].create_batch(size=3, album=album)
-    expected = {
-        'album': serializers.GetAlbumSerializer(album).data
-    }
-    response = logged_in_api_client.get(url, {'f': f, 'id': album.pk})
+    url = reverse("api:subsonic-get-album")
+    assert url.endswith("getAlbum") is True
+    artist = factories["music.Artist"]()
+    album = factories["music.Album"](artist=artist)
+    tracks = factories["music.Track"].create_batch(size=3, album=album)
+    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
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_stream(f, db, logged_in_api_client, factories, mocker):
-    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)
-    tf = factories['music.TrackFile'](track=track)
-    response = logged_in_api_client.get(url, {'f': f, 'id': track.pk})
-
-    mocked_serve.assert_called_once_with(
-        track_file=tf
-    )
+    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)
+    tf = factories["music.TrackFile"](track=track)
+    response = logged_in_api_client.get(url, {"f": f, "id": track.pk})
+
+    mocked_serve.assert_called_once_with(track_file=tf)
     assert response.status_code == 200
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_star(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-star')
-    assert url.endswith('star') is True
-    track = factories['music.Track']()
-    response = logged_in_api_client.get(url, {'f': f, 'id': track.pk})
+    url = reverse("api:subsonic-star")
+    assert url.endswith("star") is True
+    track = factories["music.Track"]()
+    response = logged_in_api_client.get(url, {"f": f, "id": track.pk})
 
     assert response.status_code == 200
-    assert response.data == {'status': 'ok'}
+    assert response.data == {"status": "ok"}
 
-    favorite = logged_in_api_client.user.track_favorites.latest('id')
+    favorite = logged_in_api_client.user.track_favorites.latest("id")
     assert favorite.track == track
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_unstar(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-unstar')
-    assert url.endswith('unstar') is True
-    track = factories['music.Track']()
-    favorite = factories['favorites.TrackFavorite'](
-        track=track, user=logged_in_api_client.user)
-    response = logged_in_api_client.get(url, {'f': f, 'id': track.pk})
+    url = reverse("api:subsonic-unstar")
+    assert url.endswith("unstar") is True
+    track = factories["music.Track"]()
+    favorite = factories["favorites.TrackFavorite"](
+        track=track, user=logged_in_api_client.user
+    )
+    response = logged_in_api_client.get(url, {"f": f, "id": track.pk})
 
     assert response.status_code == 200
-    assert response.data == {'status': 'ok'}
+    assert response.data == {"status": "ok"}
     assert logged_in_api_client.user.track_favorites.count() == 0
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_get_starred2(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-get-starred2')
-    assert url.endswith('getStarred2') is True
-    track = factories['music.Track']()
-    favorite = factories['favorites.TrackFavorite'](
-        track=track, user=logged_in_api_client.user)
-    response = logged_in_api_client.get(url, {'f': f, 'id': track.pk})
+    url = reverse("api:subsonic-get-starred2")
+    assert url.endswith("getStarred2") is True
+    track = factories["music.Track"]()
+    favorite = factories["favorites.TrackFavorite"](
+        track=track, user=logged_in_api_client.user
+    )
+    response = logged_in_api_client.get(url, {"f": f, "id": track.pk})
 
     assert response.status_code == 200
     assert response.data == {
-        'starred2': {
-            'song': serializers.get_starred_tracks_data([favorite])
-        }
+        "starred2": {"song": serializers.get_starred_tracks_data([favorite])}
     }
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_get_starred(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-get-starred')
-    assert url.endswith('getStarred') is True
-    track = factories['music.Track']()
-    favorite = factories['favorites.TrackFavorite'](
-        track=track, user=logged_in_api_client.user)
-    response = logged_in_api_client.get(url, {'f': f, 'id': track.pk})
+    url = reverse("api:subsonic-get-starred")
+    assert url.endswith("getStarred") is True
+    track = factories["music.Track"]()
+    favorite = factories["favorites.TrackFavorite"](
+        track=track, user=logged_in_api_client.user
+    )
+    response = logged_in_api_client.get(url, {"f": f, "id": track.pk})
 
     assert response.status_code == 200
     assert response.data == {
-        'starred': {
-            'song': serializers.get_starred_tracks_data([favorite])
-        }
+        "starred": {"song": serializers.get_starred_tracks_data([favorite])}
     }
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_get_album_list2(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-get-album-list2')
-    assert url.endswith('getAlbumList2') is True
-    album1 = factories['music.Album']()
-    album2 = factories['music.Album']()
-    response = logged_in_api_client.get(url, {'f': f, 'type': 'newest'})
+    url = reverse("api:subsonic-get-album-list2")
+    assert url.endswith("getAlbumList2") is True
+    album1 = factories["music.Album"]()
+    album2 = factories["music.Album"]()
+    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])
-        }
+        "albumList2": {"album": serializers.get_album_list2_data([album2, album1])}
     }
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 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')
-    factories['music.Artist'](name='nope')
-    album = factories['music.Album'](title='testvalue')
-    factories['music.Album'](title='nope')
-    track = factories['music.Track'](title='testvalue')
-    factories['music.Track'](title='nope')
-
-    response = logged_in_api_client.get(url, {'f': f, 'query': 'testval'})
-
-    artist_qs = music_models.Artist.objects.with_albums_count().filter(
-        pk=artist.pk).values('_albums_count', 'id', 'name')
+    url = reverse("api:subsonic-search3")
+    assert url.endswith("search3") is True
+    artist = factories["music.Artist"](name="testvalue")
+    factories["music.Artist"](name="nope")
+    album = factories["music.Album"](title="testvalue")
+    factories["music.Album"](title="nope")
+    track = factories["music.Track"](title="testvalue")
+    factories["music.Track"](title="nope")
+
+    response = logged_in_api_client.get(url, {"f": f, "query": "testval"})
+
+    artist_qs = (
+        music_models.Artist.objects.with_albums_count()
+        .filter(pk=artist.pk)
+        .values("_albums_count", "id", "name")
+    )
     assert response.status_code == 200
     assert response.data == {
-        'searchResult3': {
-            'artist': [serializers.get_artist_data(a) for a in artist_qs],
-            'album': serializers.get_album_list2_data([album]),
-            'song': serializers.get_song_list_data([track]),
+        "searchResult3": {
+            "artist": [serializers.get_artist_data(a) for a in artist_qs],
+            "album": serializers.get_album_list2_data([album]),
+            "song": serializers.get_song_list_data([track]),
         }
     }
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_get_playlists(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-get-playlists')
-    assert url.endswith('getPlaylists') is True
-    playlist = factories['playlists.Playlist'](
-        user=logged_in_api_client.user
-    )
-    response = logged_in_api_client.get(url, {'f': f})
+    url = reverse("api:subsonic-get-playlists")
+    assert url.endswith("getPlaylists") is True
+    playlist = factories["playlists.Playlist"](user=logged_in_api_client.user)
+    response = logged_in_api_client.get(url, {"f": f})
 
     qs = playlist.__class__.objects.with_tracks_count()
     assert response.status_code == 200
     assert response.data == {
-        'playlists': {
-            'playlist': [serializers.get_playlist_data(qs.first())],
-        }
+        "playlists": {"playlist": [serializers.get_playlist_data(qs.first())]}
     }
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_get_playlist(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-get-playlist')
-    assert url.endswith('getPlaylist') is True
-    playlist = factories['playlists.Playlist'](
-        user=logged_in_api_client.user
-    )
-    response = logged_in_api_client.get(url, {'f': f, 'id': playlist.pk})
+    url = reverse("api:subsonic-get-playlist")
+    assert url.endswith("getPlaylist") is True
+    playlist = factories["playlists.Playlist"](user=logged_in_api_client.user)
+    response = logged_in_api_client.get(url, {"f": f, "id": playlist.pk})
 
     qs = playlist.__class__.objects.with_tracks_count()
     assert response.status_code == 200
     assert response.data == {
-        'playlist': serializers.get_playlist_detail_data(qs.first())
+        "playlist": serializers.get_playlist_detail_data(qs.first())
     }
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_update_playlist(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-update-playlist')
-    assert url.endswith('updatePlaylist') is True
-    playlist = factories['playlists.Playlist'](
-        user=logged_in_api_client.user
-    )
-    plt = factories['playlists.PlaylistTrack'](
-        index=0, playlist=playlist)
-    new_track = factories['music.Track']()
+    url = reverse("api:subsonic-update-playlist")
+    assert url.endswith("updatePlaylist") is True
+    playlist = factories["playlists.Playlist"](user=logged_in_api_client.user)
+    plt = factories["playlists.PlaylistTrack"](index=0, playlist=playlist)
+    new_track = factories["music.Track"]()
     response = logged_in_api_client.get(
-        url, {
-            'f': f,
-            'name': 'new_name',
-            'playlistId': playlist.pk,
-            'songIdToAdd': new_track.pk,
-            'songIndexToRemove': 0})
+        url,
+        {
+            "f": f,
+            "name": "new_name",
+            "playlistId": playlist.pk,
+            "songIdToAdd": new_track.pk,
+            "songIndexToRemove": 0,
+        },
+    )
     playlist.refresh_from_db()
     assert response.status_code == 200
-    assert playlist.name == 'new_name'
+    assert playlist.name == "new_name"
     assert playlist.playlist_tracks.count() == 1
     assert playlist.playlist_tracks.first().track_id == new_track.pk
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_delete_playlist(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-delete-playlist')
-    assert url.endswith('deletePlaylist') is True
-    playlist = factories['playlists.Playlist'](
-        user=logged_in_api_client.user
-    )
-    response = logged_in_api_client.get(
-        url, {'f': f, 'id': playlist.pk})
+    url = reverse("api:subsonic-delete-playlist")
+    assert url.endswith("deletePlaylist") is True
+    playlist = factories["playlists.Playlist"](user=logged_in_api_client.user)
+    response = logged_in_api_client.get(url, {"f": f, "id": playlist.pk})
     assert response.status_code == 200
     with pytest.raises(playlist.__class__.DoesNotExist):
         playlist.refresh_from_db()
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_create_playlist(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-create-playlist')
-    assert url.endswith('createPlaylist') is True
-    track1 = factories['music.Track']()
-    track2 = factories['music.Track']()
+    url = reverse("api:subsonic-create-playlist")
+    assert url.endswith("createPlaylist") is True
+    track1 = factories["music.Track"]()
+    track2 = factories["music.Track"]()
     response = logged_in_api_client.get(
-        url, {'f': f, 'name': 'hello', 'songId': [track1.pk, track2.pk]})
+        url, {"f": f, "name": "hello", "songId": [track1.pk, track2.pk]}
+    )
     assert response.status_code == 200
-    playlist = logged_in_api_client.user.playlists.latest('id')
+    playlist = logged_in_api_client.user.playlists.latest("id")
     assert playlist.playlist_tracks.count() == 2
     for i, t in enumerate([track1, track2]):
         plt = playlist.playlist_tracks.get(track=t)
         assert plt.index == i
-    assert playlist.name == 'hello'
+    assert playlist.name == "hello"
     qs = playlist.__class__.objects.with_tracks_count()
     assert response.data == {
-        'playlist': serializers.get_playlist_detail_data(qs.first())
+        "playlist": serializers.get_playlist_detail_data(qs.first())
     }
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_get_music_folders(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-get-music-folders')
-    assert url.endswith('getMusicFolders') is True
-    response = logged_in_api_client.get(url, {'f': f})
+    url = reverse("api:subsonic-get-music-folders")
+    assert url.endswith("getMusicFolders") is True
+    response = logged_in_api_client.get(url, {"f": f})
     assert response.status_code == 200
     assert response.data == {
-        'musicFolders': {
-            'musicFolder': [{
-                'id': 1,
-                'name': 'Music'
-            }]
-        }
+        "musicFolders": {"musicFolder": [{"id": 1, "name": "Music"}]}
     }
 
 
-@pytest.mark.parametrize('f', ['xml', 'json'])
+@pytest.mark.parametrize("f", ["xml", "json"])
 def test_get_indexes(f, db, logged_in_api_client, factories):
-    url = reverse('api:subsonic-get-indexes')
-    assert url.endswith('getIndexes') is True
-    artists = factories['music.Artist'].create_batch(size=10)
+    url = reverse("api:subsonic-get-indexes")
+    assert url.endswith("getIndexes") is True
+    artists = factories["music.Artist"].create_batch(size=10)
     expected = {
-        'indexes': serializers.GetArtistsSerializer(
+        "indexes": serializers.GetArtistsSerializer(
             music_models.Artist.objects.all()
         ).data
     }
@@ -394,27 +363,26 @@ def test_get_indexes(f, db, logged_in_api_client, factories):
 
 
 def test_get_cover_art_album(factories, logged_in_api_client):
-    url = reverse('api:subsonic-get-cover-art')
-    assert url.endswith('getCoverArt') is True
-    album = factories['music.Album']()
-    response = logged_in_api_client.get(url, {'id': 'al-{}'.format(album.pk)})
+    url = reverse("api:subsonic-get-cover-art")
+    assert url.endswith("getCoverArt") is True
+    album = factories["music.Album"]()
+    response = logged_in_api_client.get(url, {"id": "al-{}".format(album.pk)})
 
     assert response.status_code == 200
-    assert response['Content-Type'] == ''
-    assert response['X-Accel-Redirect'] == music_views.get_file_path(
+    assert response["Content-Type"] == ""
+    assert response["X-Accel-Redirect"] == music_views.get_file_path(
         album.cover
-    ).decode('utf-8')
+    ).decode("utf-8")
 
 
 def test_scrobble(factories, logged_in_api_client):
-    tf = factories['music.TrackFile']()
+    tf = factories["music.TrackFile"]()
     track = tf.track
-    url = reverse('api:subsonic-scrobble')
-    assert url.endswith('scrobble') is True
-    response = logged_in_api_client.get(
-        url, {'id': track.pk, 'submission': True})
+    url = reverse("api:subsonic-scrobble")
+    assert url.endswith("scrobble") is True
+    response = logged_in_api_client.get(url, {"id": track.pk, "submission": True})
 
     assert response.status_code == 200
 
-    l = logged_in_api_client.user.listenings.latest('id')
+    l = logged_in_api_client.user.listenings.latest("id")
     assert l.track == track
diff --git a/api/tests/test_acoustid.py b/api/tests/test_acoustid.py
index 1f7de9247e..ab3dfd1d87 100644
--- a/api/tests/test_acoustid.py
+++ b/api/tests/test_acoustid.py
@@ -2,33 +2,42 @@ from funkwhale_api.providers.acoustid import get_acoustid_client
 
 
 def test_client_is_configured_with_correct_api_key(preferences):
-    api_key = 'hello world'
-    preferences['providers_acoustid__api_key'] = api_key
+    api_key = "hello world"
+    preferences["providers_acoustid__api_key"] = api_key
 
     client = get_acoustid_client()
     assert client.api_key == api_key
 
 
 def test_client_returns_raw_results(db, mocker, preferences):
-    api_key = 'test'
-    preferences['providers_acoustid__api_key'] = api_key
+    api_key = "test"
+    preferences["providers_acoustid__api_key"] = api_key
     payload = {
-        'results': [
-            {'id': 'e475bf79-c1ce-4441-bed7-1e33f226c0a2',
-             'recordings': [
-                {'artists': [
-                    {'id': '9c6bddde-6228-4d9f-ad0d-03f6fcb19e13',
-                     'name': 'Binärpilot'}],
-                 'duration': 268,
-                 'id': 'f269d497-1cc0-4ae4-a0c4-157ec7d73fcb',
-                 'title': 'Bend'}],
-           'score': 0.860825}],
-       'status': 'ok'
+        "results": [
+            {
+                "id": "e475bf79-c1ce-4441-bed7-1e33f226c0a2",
+                "recordings": [
+                    {
+                        "artists": [
+                            {
+                                "id": "9c6bddde-6228-4d9f-ad0d-03f6fcb19e13",
+                                "name": "Binärpilot",
+                            }
+                        ],
+                        "duration": 268,
+                        "id": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb",
+                        "title": "Bend",
+                    }
+                ],
+                "score": 0.860825,
+            }
+        ],
+        "status": "ok",
     }
 
-    m = mocker.patch('acoustid.match', return_value=payload)
+    m = mocker.patch("acoustid.match", return_value=payload)
     client = get_acoustid_client()
-    response = client.match('/tmp/noopfile.mp3')
+    response = client.match("/tmp/noopfile.mp3")
 
     assert response == payload
-    m.assert_called_once_with('test', '/tmp/noopfile.mp3', parse=False)
+    m.assert_called_once_with("test", "/tmp/noopfile.mp3", parse=False)
diff --git a/api/tests/test_downloader.py b/api/tests/test_downloader.py
index ede7bb16cc..0a41343935 100644
--- a/api/tests/test_downloader.py
+++ b/api/tests/test_downloader.py
@@ -5,7 +5,7 @@ from funkwhale_api import downloader
 
 def test_can_download_audio_from_youtube_url_to_vorbis(tmpdir):
     data = downloader.download(
-        'https://www.youtube.com/watch?v=tPEE9ZwTmy0',
-        target_directory=tmpdir)
-    assert data['audio_file_path'] == os.path.join(tmpdir, 'tPEE9ZwTmy0.ogg')
-    assert os.path.exists(data['audio_file_path'])
+        "https://www.youtube.com/watch?v=tPEE9ZwTmy0", target_directory=tmpdir
+    )
+    assert data["audio_file_path"] == os.path.join(tmpdir, "tPEE9ZwTmy0.ogg")
+    assert os.path.exists(data["audio_file_path"])
diff --git a/api/tests/test_import_audio_file.py b/api/tests/test_import_audio_file.py
index da3d1959cb..4346a47775 100644
--- a/api/tests/test_import_audio_file.py
+++ b/api/tests/test_import_audio_file.py
@@ -9,193 +9,160 @@ from django.core.management.base import CommandError
 from funkwhale_api.providers.audiofile import tasks
 from funkwhale_api.music import tasks as music_tasks
 
-DATA_DIR = os.path.join(
-    os.path.dirname(os.path.abspath(__file__)),
-    'files'
-)
+DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "files")
 
 
 def test_can_create_track_from_file_metadata_no_mbid(db, mocker):
     metadata = {
-        'artist': ['Test artist'],
-        'album': ['Test album'],
-        'title': ['Test track'],
-        'TRACKNUMBER': ['4'],
-        'date': ['2012-08-15'],
+        "artist": ["Test artist"],
+        "album": ["Test album"],
+        "title": ["Test track"],
+        "TRACKNUMBER": ["4"],
+        "date": ["2012-08-15"],
     }
-    m1 = mocker.patch('mutagen.File', return_value=metadata)
+    m1 = mocker.patch("mutagen.File", return_value=metadata)
     m2 = mocker.patch(
-        'funkwhale_api.music.metadata.Metadata.get_file_type',
-        return_value='OggVorbis',
+        "funkwhale_api.music.metadata.Metadata.get_file_type", return_value="OggVorbis"
     )
-    track = tasks.import_track_data_from_path(
-        os.path.join(DATA_DIR, 'dummy_file.ogg'))
+    track = tasks.import_track_data_from_path(os.path.join(DATA_DIR, "dummy_file.ogg"))
 
-    assert track.title == metadata['title'][0]
+    assert track.title == metadata["title"][0]
     assert track.mbid is None
     assert track.position == 4
-    assert track.album.title == metadata['album'][0]
+    assert track.album.title == metadata["album"][0]
     assert track.album.mbid is None
     assert track.album.release_date == datetime.date(2012, 8, 15)
-    assert track.artist.name == metadata['artist'][0]
+    assert track.artist.name == metadata["artist"][0]
     assert track.artist.mbid is None
 
 
 def test_can_create_track_from_file_metadata_mbid(factories, mocker):
-    album = factories['music.Album']()
+    album = factories["music.Album"]()
     mocker.patch(
-        'funkwhale_api.music.models.Album.get_or_create_from_api',
+        "funkwhale_api.music.models.Album.get_or_create_from_api",
         return_value=(album, True),
     )
 
     album_data = {
-        'release': {
-            'id': album.mbid,
-            'medium-list': [
+        "release": {
+            "id": album.mbid,
+            "medium-list": [
                 {
-                    'track-list': [
+                    "track-list": [
                         {
-                            'id': '03baca8b-855a-3c05-8f3d-d3235287d84d',
-                            'position': '4',
-                            'number': '4',
-                            'recording': {
-                                'id': '2109e376-132b-40ad-b993-2bb6812e19d4',
-                                'title': 'Teen Age Riot',
+                            "id": "03baca8b-855a-3c05-8f3d-d3235287d84d",
+                            "position": "4",
+                            "number": "4",
+                            "recording": {
+                                "id": "2109e376-132b-40ad-b993-2bb6812e19d4",
+                                "title": "Teen Age Riot",
                             },
                         }
                     ],
-                    'track-count': 1
+                    "track-count": 1,
                 }
             ],
         }
     }
-    mocker.patch(
-        'funkwhale_api.musicbrainz.api.releases.get',
-        return_value=album_data)
-    track_data = album_data['release']['medium-list'][0]['track-list'][0]
+    mocker.patch("funkwhale_api.musicbrainz.api.releases.get", return_value=album_data)
+    track_data = album_data["release"]["medium-list"][0]["track-list"][0]
     metadata = {
-        'musicbrainz_albumid': [album.mbid],
-        'musicbrainz_trackid': [track_data['recording']['id']],
+        "musicbrainz_albumid": [album.mbid],
+        "musicbrainz_trackid": [track_data["recording"]["id"]],
     }
-    m1 = mocker.patch('mutagen.File', return_value=metadata)
+    m1 = mocker.patch("mutagen.File", return_value=metadata)
     m2 = mocker.patch(
-        'funkwhale_api.music.metadata.Metadata.get_file_type',
-        return_value='OggVorbis',
+        "funkwhale_api.music.metadata.Metadata.get_file_type", return_value="OggVorbis"
     )
-    track = tasks.import_track_data_from_path(
-        os.path.join(DATA_DIR, 'dummy_file.ogg'))
+    track = tasks.import_track_data_from_path(os.path.join(DATA_DIR, "dummy_file.ogg"))
 
-    assert track.title == track_data['recording']['title']
-    assert track.mbid == track_data['recording']['id']
+    assert track.title == track_data["recording"]["title"]
+    assert track.mbid == track_data["recording"]["id"]
     assert track.position == 4
     assert track.album == album
     assert track.artist == album.artist
 
 
 def test_management_command_requires_a_valid_username(factories, mocker):
-    path = os.path.join(DATA_DIR, 'dummy_file.ogg')
-    user = factories['users.User'](username='me')
+    path = os.path.join(DATA_DIR, "dummy_file.ogg")
+    user = factories["users.User"](username="me")
     mocker.patch(
-        'funkwhale_api.providers.audiofile.management.commands.import_files.Command.do_import',  # noqa
-        return_value=(mocker.MagicMock(), []))
+        "funkwhale_api.providers.audiofile.management.commands.import_files.Command.do_import",  # noqa
+        return_value=(mocker.MagicMock(), []),
+    )
     with pytest.raises(CommandError):
-        call_command('import_files', path, username='not_me', interactive=False)
-    call_command('import_files', path, username='me', interactive=False)
+        call_command("import_files", path, username="not_me", interactive=False)
+    call_command("import_files", path, username="me", interactive=False)
 
 
 def test_in_place_import_only_from_music_dir(factories, settings):
-    user = factories['users.User'](username='me')
-    settings.MUSIC_DIRECTORY_PATH = '/nope'
-    path = os.path.join(DATA_DIR, 'dummy_file.ogg')
+    user = factories["users.User"](username="me")
+    settings.MUSIC_DIRECTORY_PATH = "/nope"
+    path = os.path.join(DATA_DIR, "dummy_file.ogg")
     with pytest.raises(CommandError):
         call_command(
-            'import_files',
-            path,
-            in_place=True,
-            username='me',
-            interactive=False
+            "import_files", path, in_place=True, username="me", interactive=False
         )
 
 
 def test_import_files_creates_a_batch_and_job(factories, mocker):
-    m = mocker.patch('funkwhale_api.music.tasks.import_job_run')
-    user = factories['users.User'](username='me')
-    path = os.path.join(DATA_DIR, 'dummy_file.ogg')
-    call_command(
-        'import_files',
-        path,
-        username='me',
-        async=False,
-        interactive=False)
+    m = mocker.patch("funkwhale_api.music.tasks.import_job_run")
+    user = factories["users.User"](username="me")
+    path = os.path.join(DATA_DIR, "dummy_file.ogg")
+    call_command("import_files", path, username="me", async=False, interactive=False)
 
-    batch = user.imports.latest('id')
-    assert batch.source == 'shell'
+    batch = user.imports.latest("id")
+    assert batch.source == "shell"
     assert batch.jobs.count() == 1
 
     job = batch.jobs.first()
 
-    assert job.status == 'pending'
-    with open(path, 'rb') as f:
+    assert job.status == "pending"
+    with open(path, "rb") as f:
         assert job.audio_file.read() == f.read()
 
-    assert job.source == 'file://' + path
-    m.assert_called_once_with(
-        import_job_id=job.pk,
-        use_acoustid=False)
+    assert job.source == "file://" + path
+    m.assert_called_once_with(import_job_id=job.pk, use_acoustid=False)
 
 
 def test_import_files_skip_if_path_already_imported(factories, mocker):
-    user = factories['users.User'](username='me')
-    path = os.path.join(DATA_DIR, 'dummy_file.ogg')
-    existing = factories['music.TrackFile'](
-        source='file://{}'.format(path))
+    user = factories["users.User"](username="me")
+    path = os.path.join(DATA_DIR, "dummy_file.ogg")
+    existing = factories["music.TrackFile"](source="file://{}".format(path))
 
-    call_command(
-        'import_files',
-        path,
-        username='me',
-        async=False,
-        interactive=False)
+    call_command("import_files", path, username="me", async=False, interactive=False)
     assert user.imports.count() == 0
 
 
 def test_import_files_works_with_utf8_file_name(factories, mocker):
-    m = mocker.patch('funkwhale_api.music.tasks.import_job_run')
-    user = factories['users.User'](username='me')
-    path = os.path.join(DATA_DIR, 'utf8-éà◌.ogg')
-    call_command(
-        'import_files',
-        path,
-        username='me',
-        async=False,
-        interactive=False)
-    batch = user.imports.latest('id')
+    m = mocker.patch("funkwhale_api.music.tasks.import_job_run")
+    user = factories["users.User"](username="me")
+    path = os.path.join(DATA_DIR, "utf8-éà◌.ogg")
+    call_command("import_files", path, username="me", async=False, interactive=False)
+    batch = user.imports.latest("id")
     job = batch.jobs.first()
-    m.assert_called_once_with(
-        import_job_id=job.pk,
-        use_acoustid=False)
+    m.assert_called_once_with(import_job_id=job.pk, use_acoustid=False)
 
 
 def test_import_files_in_place(factories, mocker, settings):
     settings.MUSIC_DIRECTORY_PATH = DATA_DIR
-    m = mocker.patch('funkwhale_api.music.tasks.import_job_run')
-    user = factories['users.User'](username='me')
-    path = os.path.join(DATA_DIR, 'utf8-éà◌.ogg')
+    m = mocker.patch("funkwhale_api.music.tasks.import_job_run")
+    user = factories["users.User"](username="me")
+    path = os.path.join(DATA_DIR, "utf8-éà◌.ogg")
     call_command(
-        'import_files',
+        "import_files",
         path,
-        username='me',
+        username="me",
         async=False,
         in_place=True,
-        interactive=False)
-    batch = user.imports.latest('id')
+        interactive=False,
+    )
+    batch = user.imports.latest("id")
     job = batch.jobs.first()
     assert bool(job.audio_file) is False
-    m.assert_called_once_with(
-        import_job_id=job.pk,
-        use_acoustid=False)
+    m.assert_called_once_with(import_job_id=job.pk, use_acoustid=False)
 
 
 def test_storage_rename_utf_8_files(factories):
-    tf = factories['music.TrackFile'](audio_file__filename='été.ogg')
-    assert tf.audio_file.name.endswith('ete.ogg')
+    tf = factories["music.TrackFile"](audio_file__filename="été.ogg")
+    assert tf.audio_file.name.endswith("ete.ogg")
diff --git a/api/tests/test_jwt_querystring.py b/api/tests/test_jwt_querystring.py
index f18e6b7292..18a673fb48 100644
--- a/api/tests/test_jwt_querystring.py
+++ b/api/tests/test_jwt_querystring.py
@@ -5,18 +5,15 @@ jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
 jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
 
 
-def test_can_authenticate_using_token_param_in_url(
-        factories, preferences, client):
-    user = factories['users.User']()
-    preferences['common__api_authentication_required'] = True
-    url = reverse('api:v1:tracks-list')
+def test_can_authenticate_using_token_param_in_url(factories, preferences, client):
+    user = factories["users.User"]()
+    preferences["common__api_authentication_required"] = True
+    url = reverse("api:v1:tracks-list")
     response = client.get(url)
 
     assert response.status_code == 401
 
     payload = jwt_payload_handler(user)
     token = jwt_encode_handler(payload)
-    response = client.get(url, data={
-        'jwt': token
-    })
+    response = client.get(url, data={"jwt": token})
     assert response.status_code == 200
diff --git a/api/tests/test_tasks.py b/api/tests/test_tasks.py
index 16088d53ff..d46c3a3fb3 100644
--- a/api/tests/test_tasks.py
+++ b/api/tests/test_tasks.py
@@ -10,24 +10,25 @@ class Dummy:
 
 
 def test_require_instance_decorator(factories, mocker):
-    user = factories['users.User']()
+    user = factories["users.User"]()
 
-    @celery.require_instance(user.__class__, 'user')
+    @celery.require_instance(user.__class__, "user")
     def t(user):
         Dummy.noop(user)
 
-    m = mocker.patch.object(Dummy, 'noop')
+    m = mocker.patch.object(Dummy, "noop")
     t(user_id=user.pk)
 
     m.assert_called_once_with(user)
 
 
 def test_require_instance_decorator_accepts_qs(factories, mocker):
-    user = factories['users.User'](is_active=False)
+    user = factories["users.User"](is_active=False)
     qs = user.__class__.objects.filter(is_active=True)
 
-    @celery.require_instance(qs, 'user')
+    @celery.require_instance(qs, "user")
     def t(user):
         pass
+
     with pytest.raises(user.__class__.DoesNotExist):
         t(user_id=user.pk)
diff --git a/api/tests/test_youtube.py b/api/tests/test_youtube.py
index 7ab6256daf..629558cc32 100644
--- a/api/tests/test_youtube.py
+++ b/api/tests/test_youtube.py
@@ -8,24 +8,25 @@ from .data import youtube as api_data
 
 def test_can_get_search_results_from_youtube(mocker):
     mocker.patch(
-        'funkwhale_api.providers.youtube.client._do_search',
-        return_value=api_data.search['8 bit adventure'])
-    query = '8 bit adventure'
+        "funkwhale_api.providers.youtube.client._do_search",
+        return_value=api_data.search["8 bit adventure"],
+    )
+    query = "8 bit adventure"
     results = client.search(query)
-    assert results[0]['id']['videoId'] == '0HxZn6CzOIo'
-    assert results[0]['snippet']['title'] == 'AdhesiveWombat - 8 Bit Adventure'
-    assert results[0]['full_url'] == 'https://www.youtube.com/watch?v=0HxZn6CzOIo'
+    assert results[0]["id"]["videoId"] == "0HxZn6CzOIo"
+    assert results[0]["snippet"]["title"] == "AdhesiveWombat - 8 Bit Adventure"
+    assert results[0]["full_url"] == "https://www.youtube.com/watch?v=0HxZn6CzOIo"
 
 
-def test_can_get_search_results_from_funkwhale(
-        preferences, mocker, api_client, db):
-    preferences['common__api_authentication_required'] = False
+def test_can_get_search_results_from_funkwhale(preferences, mocker, api_client, db):
+    preferences["common__api_authentication_required"] = False
     mocker.patch(
-        'funkwhale_api.providers.youtube.client._do_search',
-        return_value=api_data.search['8 bit adventure'])
-    query = '8 bit adventure'
-    url = reverse('api:v1:providers:youtube:search')
-    response = api_client.get(url, {'query': query})
+        "funkwhale_api.providers.youtube.client._do_search",
+        return_value=api_data.search["8 bit adventure"],
+    )
+    query = "8 bit adventure"
+    url = reverse("api:v1:providers:youtube:search")
+    response = api_client.get(url, {"query": query})
     # we should cast the youtube result to something more generic
     expected = {
         "id": "0HxZn6CzOIo",
@@ -36,7 +37,7 @@ def test_can_get_search_results_from_funkwhale(
         "title": "AdhesiveWombat - 8 Bit Adventure",
         "channelTitle": "AdhesiveWombat",
         "publishedAt": "2012-08-22T18:41:03.000Z",
-        "cover": "https://i.ytimg.com/vi/0HxZn6CzOIo/hqdefault.jpg"
+        "cover": "https://i.ytimg.com/vi/0HxZn6CzOIo/hqdefault.jpg",
     }
 
     assert response.data[0] == expected
@@ -44,41 +45,37 @@ def test_can_get_search_results_from_funkwhale(
 
 def test_can_send_multiple_queries_at_once(mocker):
     mocker.patch(
-        'funkwhale_api.providers.youtube.client._do_search',
+        "funkwhale_api.providers.youtube.client._do_search",
         side_effect=[
-            api_data.search['8 bit adventure'],
-            api_data.search['system of a down toxicity'],
-        ]
+            api_data.search["8 bit adventure"],
+            api_data.search["system of a down toxicity"],
+        ],
     )
 
     queries = OrderedDict()
-    queries['1'] = {
-        'q': '8 bit adventure',
-    }
-    queries['2'] = {
-        'q': 'system of a down toxicity',
-    }
+    queries["1"] = {"q": "8 bit adventure"}
+    queries["2"] = {"q": "system of a down toxicity"}
 
     results = client.search_multiple(queries)
 
-    assert results['1'][0]['id']['videoId'] == '0HxZn6CzOIo'
-    assert results['1'][0]['snippet']['title'] == 'AdhesiveWombat - 8 Bit Adventure'
-    assert results['1'][0]['full_url'] == 'https://www.youtube.com/watch?v=0HxZn6CzOIo'
-    assert results['2'][0]['id']['videoId'] == 'BorYwGi2SJc'
-    assert results['2'][0]['snippet']['title'] == 'System of a Down: Toxicity'
-    assert results['2'][0]['full_url'] == 'https://www.youtube.com/watch?v=BorYwGi2SJc'
+    assert results["1"][0]["id"]["videoId"] == "0HxZn6CzOIo"
+    assert results["1"][0]["snippet"]["title"] == "AdhesiveWombat - 8 Bit Adventure"
+    assert results["1"][0]["full_url"] == "https://www.youtube.com/watch?v=0HxZn6CzOIo"
+    assert results["2"][0]["id"]["videoId"] == "BorYwGi2SJc"
+    assert results["2"][0]["snippet"]["title"] == "System of a Down: Toxicity"
+    assert results["2"][0]["full_url"] == "https://www.youtube.com/watch?v=BorYwGi2SJc"
 
 
 def test_can_send_multiple_queries_at_once_from_funwkhale(
-        preferences, mocker, db, api_client):
-    preferences['common__api_authentication_required'] = False
+    preferences, mocker, db, api_client
+):
+    preferences["common__api_authentication_required"] = False
     mocker.patch(
-        'funkwhale_api.providers.youtube.client._do_search',
-        return_value=api_data.search['8 bit adventure'])
+        "funkwhale_api.providers.youtube.client._do_search",
+        return_value=api_data.search["8 bit adventure"],
+    )
     queries = OrderedDict()
-    queries['1'] = {
-        'q': '8 bit adventure',
-    }
+    queries["1"] = {"q": "8 bit adventure"}
 
     expected = {
         "id": "0HxZn6CzOIo",
@@ -89,10 +86,10 @@ def test_can_send_multiple_queries_at_once_from_funwkhale(
         "title": "AdhesiveWombat - 8 Bit Adventure",
         "channelTitle": "AdhesiveWombat",
         "publishedAt": "2012-08-22T18:41:03.000Z",
-        "cover": "https://i.ytimg.com/vi/0HxZn6CzOIo/hqdefault.jpg"
+        "cover": "https://i.ytimg.com/vi/0HxZn6CzOIo/hqdefault.jpg",
     }
 
-    url = reverse('api:v1:providers:youtube:searchs')
-    response = api_client.post(url, queries, format='json')
+    url = reverse("api:v1:providers:youtube:searchs")
+    response = api_client.post(url, queries, format="json")
 
-    assert expected == response.data['1'][0]
+    assert expected == response.data["1"][0]
diff --git a/api/tests/users/test_activity.py b/api/tests/users/test_activity.py
index 26d0b11f8a..cfacff9975 100644
--- a/api/tests/users/test_activity.py
+++ b/api/tests/users/test_activity.py
@@ -2,13 +2,14 @@ from funkwhale_api.users import serializers
 
 
 def test_get_user_activity_url(settings, factories):
-    user = factories['users.User']()
-    assert user.get_activity_url() == '{}/@{}'.format(
-        settings.FUNKWHALE_URL, user.username)
+    user = factories["users.User"]()
+    assert user.get_activity_url() == "{}/@{}".format(
+        settings.FUNKWHALE_URL, user.username
+    )
 
 
 def test_activity_user_serializer(factories):
-    user = factories['users.User']()
+    user = factories["users.User"]()
 
     expected = {
         "type": "Person",
diff --git a/api/tests/users/test_admin.py b/api/tests/users/test_admin.py
index 7645a02953..03b316eb0d 100644
--- a/api/tests/users/test_admin.py
+++ b/api/tests/users/test_admin.py
@@ -3,28 +3,24 @@ from funkwhale_api.users.admin import MyUserCreationForm
 
 def test_clean_username_success(db):
     # Instantiate the form with a new username
-    form = MyUserCreationForm({
-        'username': 'alamode',
-        'password1': '123456',
-        'password2': '123456',
-    })
+    form = MyUserCreationForm(
+        {"username": "alamode", "password1": "123456", "password2": "123456"}
+    )
     # Run is_valid() to trigger the validation
     valid = form.is_valid()
     assert valid
 
     # Run the actual clean_username method
     username = form.clean_username()
-    assert 'alamode' == username
+    assert "alamode" == username
 
 
 def test_clean_username_false(factories):
-    user = factories['users.User']()
+    user = factories["users.User"]()
     # Instantiate the form with the same username as self.user
-    form = MyUserCreationForm({
-        'username': user.username,
-        'password1': '123456',
-        'password2': '123456',
-    })
+    form = MyUserCreationForm(
+        {"username": user.username, "password1": "123456", "password2": "123456"}
+    )
     # Run is_valid() to trigger the validation, which is going to fail
     # because the username is already taken
     valid = form.is_valid()
@@ -32,4 +28,4 @@ def test_clean_username_false(factories):
 
     # The form.errors dict should contain a single error called 'username'
     assert len(form.errors) == 1
-    assert 'username' in form.errors
+    assert "username" in form.errors
diff --git a/api/tests/users/test_jwt.py b/api/tests/users/test_jwt.py
index d264494e59..28ff09d746 100644
--- a/api/tests/users/test_jwt.py
+++ b/api/tests/users/test_jwt.py
@@ -6,8 +6,9 @@ from rest_framework_jwt.settings import api_settings
 
 from funkwhale_api.users.models import User
 
+
 def test_can_invalidate_token_when_changing_user_secret_key(factories):
-    user = factories['users.User']()
+    user = factories["users.User"]()
     u1 = user.secret_key
     jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
     jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
diff --git a/api/tests/users/test_models.py b/api/tests/users/test_models.py
index 42123b5e86..c73a4a1b1a 100644
--- a/api/tests/users/test_models.py
+++ b/api/tests/users/test_models.py
@@ -4,26 +4,26 @@ from funkwhale_api.users import models
 
 
 def test__str__(factories):
-    user = factories['users.User'](username='hello')
-    assert user.__str__() == 'hello'
+    user = factories["users.User"](username="hello")
+    assert user.__str__() == "hello"
 
 
 def test_changing_password_updates_subsonic_api_token_no_token(factories):
-    user = factories['users.User'](subsonic_api_token=None)
-    user.set_password('new')
+    user = factories["users.User"](subsonic_api_token=None)
+    user.set_password("new")
     assert user.subsonic_api_token is None
 
 
 def test_changing_password_updates_subsonic_api_token(factories):
-    user = factories['users.User'](subsonic_api_token='test')
-    user.set_password('new')
+    user = factories["users.User"](subsonic_api_token="test")
+    user.set_password("new")
 
     assert user.subsonic_api_token is not None
-    assert user.subsonic_api_token != 'test'
+    assert user.subsonic_api_token != "test"
 
 
 def test_get_permissions_superuser(factories):
-    user = factories['users.User'](is_superuser=True)
+    user = factories["users.User"](is_superuser=True)
 
     perms = user.get_permissions()
     for p in models.PERMISSIONS:
@@ -31,44 +31,50 @@ def test_get_permissions_superuser(factories):
 
 
 def test_get_permissions_regular(factories):
-    user = factories['users.User'](permission_library=True)
+    user = factories["users.User"](permission_library=True)
 
     perms = user.get_permissions()
     for p in models.PERMISSIONS:
-        if p == 'library':
+        if p == "library":
             assert perms[p] is True
         else:
             assert perms[p] is False
 
 
 def test_get_permissions_default(factories, preferences):
-    preferences['users__default_permissions'] = ['upload', 'federation']
-    user = factories['users.User']()
+    preferences["users__default_permissions"] = ["upload", "federation"]
+    user = factories["users.User"]()
 
     perms = user.get_permissions()
-    assert perms['upload'] is True
-    assert perms['federation'] is True
-    assert perms['library'] is False
-    assert perms['settings'] is False
-
-
-@pytest.mark.parametrize('args,perms,expected', [
-    ({'is_superuser': True}, ['federation', 'library'], True),
-    ({'is_superuser': False}, ['federation'], False),
-    ({'permission_library': True}, ['library'], True),
-    ({'permission_library': True}, ['library', 'federation'], False),
-])
+    assert perms["upload"] is True
+    assert perms["federation"] is True
+    assert perms["library"] is False
+    assert perms["settings"] is False
+
+
+@pytest.mark.parametrize(
+    "args,perms,expected",
+    [
+        ({"is_superuser": True}, ["federation", "library"], True),
+        ({"is_superuser": False}, ["federation"], False),
+        ({"permission_library": True}, ["library"], True),
+        ({"permission_library": True}, ["library", "federation"], False),
+    ],
+)
 def test_has_permissions_and(args, perms, expected, factories):
-    user = factories['users.User'](**args)
-    assert user.has_permissions(*perms, operator='and') is expected
-
-
-@pytest.mark.parametrize('args,perms,expected', [
-    ({'is_superuser': True}, ['federation', 'library'], True),
-    ({'is_superuser': False}, ['federation'], False),
-    ({'permission_library': True}, ['library', 'federation'], True),
-    ({'permission_library': True}, ['federation'], False),
-])
+    user = factories["users.User"](**args)
+    assert user.has_permissions(*perms, operator="and") is expected
+
+
+@pytest.mark.parametrize(
+    "args,perms,expected",
+    [
+        ({"is_superuser": True}, ["federation", "library"], True),
+        ({"is_superuser": False}, ["federation"], False),
+        ({"permission_library": True}, ["library", "federation"], True),
+        ({"permission_library": True}, ["federation"], False),
+    ],
+)
 def test_has_permissions_or(args, perms, expected, factories):
-    user = factories['users.User'](**args)
-    assert user.has_permissions(*perms, operator='or') is expected
+    user = factories["users.User"](**args)
+    assert user.has_permissions(*perms, operator="or") is expected
diff --git a/api/tests/users/test_permissions.py b/api/tests/users/test_permissions.py
index 518ccd1c80..f4b5010180 100644
--- a/api/tests/users/test_permissions.py
+++ b/api/tests/users/test_permissions.py
@@ -7,76 +7,88 @@ from funkwhale_api.users import permissions
 def test_has_user_permission_no_user(api_request):
     view = APIView.as_view()
     permission = permissions.HasUserPermission()
-    request = api_request.get('/')
+    request = api_request.get("/")
     assert permission.has_permission(request, view) is False
 
 
 def test_has_user_permission_anonymous(anonymous_user, api_request):
     view = APIView.as_view()
     permission = permissions.HasUserPermission()
-    request = api_request.get('/')
-    setattr(request, 'user', anonymous_user)
+    request = api_request.get("/")
+    setattr(request, "user", anonymous_user)
     assert permission.has_permission(request, view) is False
 
 
-@pytest.mark.parametrize('value', [True, False])
+@pytest.mark.parametrize("value", [True, False])
 def test_has_user_permission_logged_in_single(value, factories, api_request):
-    user = factories['users.User'](permission_federation=value)
+    user = factories["users.User"](permission_federation=value)
 
     class View(APIView):
-        required_permissions = ['federation']
+        required_permissions = ["federation"]
+
     view = View()
     permission = permissions.HasUserPermission()
-    request = api_request.get('/')
-    setattr(request, 'user', user)
+    request = api_request.get("/")
+    setattr(request, "user", user)
     result = permission.has_permission(request, view)
-    assert result == user.has_permissions('federation') == value
+    assert result == user.has_permissions("federation") == value
 
 
-@pytest.mark.parametrize('federation,library,expected', [
-    (True, False, False),
-    (False, True, False),
-    (False, False, False),
-    (True, True, True),
-])
+@pytest.mark.parametrize(
+    "federation,library,expected",
+    [
+        (True, False, False),
+        (False, True, False),
+        (False, False, False),
+        (True, True, True),
+    ],
+)
 def test_has_user_permission_logged_in_multiple_and(
-        federation, library, expected, factories, api_request):
-    user = factories['users.User'](
-        permission_federation=federation,
-        permission_library=library,
+    federation, library, expected, factories, api_request
+):
+    user = factories["users.User"](
+        permission_federation=federation, permission_library=library
     )
 
     class View(APIView):
-        required_permissions = ['federation', 'library']
-        permission_operator = 'and'
+        required_permissions = ["federation", "library"]
+        permission_operator = "and"
+
     view = View()
     permission = permissions.HasUserPermission()
-    request = api_request.get('/')
-    setattr(request, 'user', user)
+    request = api_request.get("/")
+    setattr(request, "user", user)
     result = permission.has_permission(request, view)
-    assert result == user.has_permissions('federation', 'library') == expected
+    assert result == user.has_permissions("federation", "library") == expected
 
 
-@pytest.mark.parametrize('federation,library,expected', [
-    (True, False, True),
-    (False, True, True),
-    (False, False, False),
-    (True, True, True),
-])
+@pytest.mark.parametrize(
+    "federation,library,expected",
+    [
+        (True, False, True),
+        (False, True, True),
+        (False, False, False),
+        (True, True, True),
+    ],
+)
 def test_has_user_permission_logged_in_multiple_or(
-        federation, library, expected, factories, api_request):
-    user = factories['users.User'](
-        permission_federation=federation,
-        permission_library=library,
+    federation, library, expected, factories, api_request
+):
+    user = factories["users.User"](
+        permission_federation=federation, permission_library=library
     )
 
     class View(APIView):
-        required_permissions = ['federation', 'library']
-        permission_operator = 'or'
+        required_permissions = ["federation", "library"]
+        permission_operator = "or"
+
     view = View()
     permission = permissions.HasUserPermission()
-    request = api_request.get('/')
-    setattr(request, 'user', user)
+    request = api_request.get("/")
+    setattr(request, "user", user)
     result = permission.has_permission(request, view)
-    assert result == user.has_permissions(
-        'federation', 'library', operator='or') == expected
+    assert (
+        result
+        == user.has_permissions("federation", "library", operator="or")
+        == expected
+    )
diff --git a/api/tests/users/test_views.py b/api/tests/users/test_views.py
index 6418889ce4..f7a11367e1 100644
--- a/api/tests/users/test_views.py
+++ b/api/tests/users/test_views.py
@@ -8,91 +8,83 @@ from funkwhale_api.users.models import User
 
 
 def test_can_create_user_via_api(preferences, api_client, db):
-    url = reverse('rest_register')
+    url = reverse("rest_register")
     data = {
-        'username': 'test1',
-        'email': 'test1@test.com',
-        'password1': 'testtest',
-        'password2': 'testtest',
+        "username": "test1",
+        "email": "test1@test.com",
+        "password1": "testtest",
+        "password2": "testtest",
     }
-    preferences['users__registration_enabled'] = True
+    preferences["users__registration_enabled"] = True
     response = api_client.post(url, data)
     assert response.status_code == 201
 
-    u = User.objects.get(email='test1@test.com')
-    assert u.username == 'test1'
+    u = User.objects.get(email="test1@test.com")
+    assert u.username == "test1"
 
 
 def test_can_restrict_usernames(settings, preferences, db, api_client):
-    url = reverse('rest_register')
-    preferences['users__registration_enabled'] = True
-    settings.USERNAME_BLACKLIST = ['funkwhale']
+    url = reverse("rest_register")
+    preferences["users__registration_enabled"] = True
+    settings.USERNAME_BLACKLIST = ["funkwhale"]
     data = {
-        'username': 'funkwhale',
-        'email': 'contact@funkwhale.io',
-        'password1': 'testtest',
-        'password2': 'testtest',
+        "username": "funkwhale",
+        "email": "contact@funkwhale.io",
+        "password1": "testtest",
+        "password2": "testtest",
     }
 
     response = api_client.post(url, data)
 
     assert response.status_code == 400
-    assert 'username' in response.data
+    assert "username" in response.data
 
 
 def test_can_disable_registration_view(preferences, api_client, db):
-    url = reverse('rest_register')
+    url = reverse("rest_register")
     data = {
-        'username': 'test1',
-        'email': 'test1@test.com',
-        'password1': 'testtest',
-        'password2': 'testtest',
+        "username": "test1",
+        "email": "test1@test.com",
+        "password1": "testtest",
+        "password2": "testtest",
     }
-    preferences['users__registration_enabled'] = False
+    preferences["users__registration_enabled"] = False
     response = api_client.post(url, data)
     assert response.status_code == 403
 
 
 def test_can_fetch_data_from_api(api_client, factories):
-    url = reverse('api:v1:users:users-me')
+    url = reverse("api:v1:users:users-me")
     response = api_client.get(url)
     # login required
     assert response.status_code == 401
 
-    user = factories['users.User'](
-        permission_library=True
-    )
-    api_client.login(username=user.username, password='test')
+    user = factories["users.User"](permission_library=True)
+    api_client.login(username=user.username, password="test")
     response = api_client.get(url)
     assert response.status_code == 200
-    assert response.data['username'] == user.username
-    assert response.data['is_staff'] == user.is_staff
-    assert response.data['is_superuser'] == user.is_superuser
-    assert response.data['email'] == user.email
-    assert response.data['name'] == user.name
-    assert response.data['permissions'] == user.get_permissions()
+    assert response.data["username"] == user.username
+    assert response.data["is_staff"] == user.is_staff
+    assert response.data["is_superuser"] == user.is_superuser
+    assert response.data["email"] == user.email
+    assert response.data["name"] == user.name
+    assert response.data["permissions"] == user.get_permissions()
 
 
 def test_can_get_token_via_api(api_client, factories):
-    user = factories['users.User']()
-    url = reverse('api:v1:token')
-    payload = {
-        'username': user.username,
-        'password': 'test'
-    }
+    user = factories["users.User"]()
+    url = reverse("api:v1:token")
+    payload = {"username": user.username, "password": "test"}
 
     response = api_client.post(url, payload)
     assert response.status_code == 200
-    assert 'token' in response.data
+    assert "token" in response.data
 
 
 def test_can_get_token_via_api_inactive(api_client, factories):
-    user = factories['users.User'](is_active=False)
-    url = reverse('api:v1:token')
-    payload = {
-        'username': user.username,
-        'password': 'test'
-    }
+    user = factories["users.User"](is_active=False)
+    url = reverse("api:v1:token")
+    payload = {"username": user.username, "password": "test"}
 
     response = api_client.post(url, payload)
     assert response.status_code == 400
@@ -100,34 +92,27 @@ def test_can_get_token_via_api_inactive(api_client, factories):
 
 def test_can_refresh_token_via_api(api_client, factories, mocker):
     # first, we get a token
-    user = factories['users.User']()
-    url = reverse('api:v1:token')
-    payload = {
-        'username': user.username,
-        'password': 'test'
-    }
+    user = factories["users.User"]()
+    url = reverse("api:v1:token")
+    payload = {"username": user.username, "password": "test"}
 
     response = api_client.post(url, payload)
     assert response.status_code == 200
 
-    token = response.data['token']
-    url = reverse('api:v1:token_refresh')
-    response = api_client.post(url, {'token': token})
+    token = response.data["token"]
+    url = reverse("api:v1:token_refresh")
+    response = api_client.post(url, {"token": token})
 
     assert response.status_code == 200
-    assert 'token' in response.data
+    assert "token" in response.data
 
 
 def test_changing_password_updates_secret_key(logged_in_api_client):
     user = logged_in_api_client.user
     password = user.password
     secret_key = user.secret_key
-    payload = {
-        'old_password': 'test',
-        'new_password1': 'new',
-        'new_password2': 'new',
-    }
-    url = reverse('change_password')
+    payload = {"old_password": "test", "new_password1": "new", "new_password2": "new"}
+    url = reverse("change_password")
 
     response = logged_in_api_client.post(url, payload)
 
@@ -137,14 +122,11 @@ def test_changing_password_updates_secret_key(logged_in_api_client):
     assert user.password != password
 
 
-def test_can_request_password_reset(
-        factories, api_client, mailoutbox):
-    user = factories['users.User']()
-    payload = {
-        'email': user.email,
-    }
+def test_can_request_password_reset(factories, api_client, mailoutbox):
+    user = factories["users.User"]()
+    payload = {"email": user.email}
     emails = len(mailoutbox)
-    url = reverse('rest_password_reset')
+    url = reverse("rest_password_reset")
 
     response = api_client.post(url, payload)
     assert response.status_code == 200
@@ -153,86 +135,76 @@ def test_can_request_password_reset(
 
 def test_user_can_patch_his_own_settings(logged_in_api_client):
     user = logged_in_api_client.user
-    payload = {
-        'privacy_level': 'me',
-    }
-    url = reverse(
-        'api:v1:users:users-detail',
-        kwargs={'username': user.username})
+    payload = {"privacy_level": "me"}
+    url = reverse("api:v1:users:users-detail", kwargs={"username": user.username})
 
     response = logged_in_api_client.patch(url, payload)
 
     assert response.status_code == 200
     user.refresh_from_db()
 
-    assert user.privacy_level == 'me'
+    assert user.privacy_level == "me"
 
 
 def test_user_can_request_new_subsonic_token(logged_in_api_client):
     user = logged_in_api_client.user
-    user.subsonic_api_token = 'test'
+    user.subsonic_api_token = "test"
     user.save()
 
     url = reverse(
-        'api:v1:users:users-subsonic-token',
-        kwargs={'username': user.username})
+        "api:v1:users:users-subsonic-token", kwargs={"username": user.username}
+    )
 
     response = logged_in_api_client.post(url)
 
     assert response.status_code == 200
     user.refresh_from_db()
-    assert user.subsonic_api_token != 'test'
+    assert user.subsonic_api_token != "test"
     assert user.subsonic_api_token is not None
-    assert response.data == {
-        'subsonic_api_token': user.subsonic_api_token
-    }
+    assert response.data == {"subsonic_api_token": user.subsonic_api_token}
 
 
 def test_user_can_get_new_subsonic_token(logged_in_api_client):
     user = logged_in_api_client.user
-    user.subsonic_api_token = 'test'
+    user.subsonic_api_token = "test"
     user.save()
 
     url = reverse(
-        'api:v1:users:users-subsonic-token',
-        kwargs={'username': user.username})
+        "api:v1:users:users-subsonic-token", kwargs={"username": user.username}
+    )
 
     response = logged_in_api_client.get(url)
 
     assert response.status_code == 200
-    assert response.data == {
-        'subsonic_api_token': 'test'
-    }
+    assert response.data == {"subsonic_api_token": "test"}
 
 
 def test_user_can_request_new_subsonic_token(logged_in_api_client):
     user = logged_in_api_client.user
-    user.subsonic_api_token = 'test'
+    user.subsonic_api_token = "test"
     user.save()
 
     url = reverse(
-        'api:v1:users:users-subsonic-token',
-        kwargs={'username': user.username})
+        "api:v1:users:users-subsonic-token", kwargs={"username": user.username}
+    )
 
     response = logged_in_api_client.post(url)
 
     assert response.status_code == 200
     user.refresh_from_db()
-    assert user.subsonic_api_token != 'test'
+    assert user.subsonic_api_token != "test"
     assert user.subsonic_api_token is not None
-    assert response.data == {
-        'subsonic_api_token': user.subsonic_api_token
-    }
+    assert response.data == {"subsonic_api_token": user.subsonic_api_token}
 
 
 def test_user_can_delete_subsonic_token(logged_in_api_client):
     user = logged_in_api_client.user
-    user.subsonic_api_token = 'test'
+    user.subsonic_api_token = "test"
     user.save()
 
     url = reverse(
-        'api:v1:users:users-subsonic-token',
-        kwargs={'username': user.username})
+        "api:v1:users:users-subsonic-token", kwargs={"username": user.username}
+    )
 
     response = logged_in_api_client.delete(url)
 
@@ -241,16 +213,11 @@ def test_user_can_delete_subsonic_token(logged_in_api_client):
     assert user.subsonic_api_token is None
 
 
-@pytest.mark.parametrize('method', ['put', 'patch'])
-def test_user_cannot_patch_another_user(
-        method, logged_in_api_client, factories):
-    user = factories['users.User']()
-    payload = {
-        'privacy_level': 'me',
-    }
-    url = reverse(
-        'api:v1:users:users-detail',
-        kwargs={'username': user.username})
+@pytest.mark.parametrize("method", ["put", "patch"])
+def test_user_cannot_patch_another_user(method, logged_in_api_client, factories):
+    user = factories["users.User"]()
+    payload = {"privacy_level": "me"}
+    url = reverse("api:v1:users:users-detail", kwargs={"username": user.username})
 
     handler = getattr(logged_in_api_client, method)
     response = handler(url, payload)
-- 
GitLab