diff --git a/CONTRIBUTING b/CONTRIBUTING
new file mode 100644
index 0000000000000000000000000000000000000000..9f4ec885038eab47d8c27cdd12b6d7ab5cb461be
--- /dev/null
+++ b/CONTRIBUTING
@@ -0,0 +1,442 @@
+Contibute to Funkwhale development
+==================================
+
+First of all, thank you for your interest in the project! We really
+appreciate the fact that you're about to take some time to read this
+and hack on the project.
+
+This document will guide you through common operations such as:
+
+- Setup your development environment
+- Working on your first issue
+- Writing unit tests to validate your work
+- Submit your work
+
+
+Setup your development environment
+----------------------------------
+
+If you want to fix a bug or implement a feature, you'll need
+to run a local, development copy of funkwhale.
+
+We provide a docker based development environment, which should
+be both easy to setup and work similarly regardless of your
+development machine setup.
+
+Instructions for bare-metal setup will come in the future (Merge requests
+are welcome).
+
+Installing docker and docker-compose
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+This is already cover in the relevant documentations:
+
+- https://docs.docker.com/install/
+- https://docs.docker.com/compose/install/
+
+Cloning the project
+^^^^^^^^^^^^^^^^^^^
+
+Visit https://code.eliotberriot.com/funkwhale/funkwhale and clone the repository using SSH or HTTPS. Exemple using SSH::
+
+    git clone ssh://git@code.eliotberriot.com:2222/funkwhale/funkwhale.git
+    cd funkwhale
+
+
+A note about branches
+^^^^^^^^^^^^^^^^^^^^^
+
+Next release development occurs on the "develop" branch, and releases are made on the "master" branch. Therefor, when submitting Merge Requests, ensure you are merging on the develop branch.
+
+
+Working with docker
+^^^^^^^^^^^^^^^^^^^
+
+In developpement, we use the docker-compose file named ``dev.yml``, and this is why all our docker-compose commands will look like this::
+
+    docker-compose -f dev.yml logs
+
+If you do not want to add the ``-f dev.yml`` snippet everytime, you can run this command before starting your work::
+
+    export COMPOSE_FILE=dev.yml
+
+
+Building the containers
+^^^^^^^^^^^^^^^^^^^^^^^
+
+On your initial clone, or if there have been some changes in the
+app dependencies, you will have to rebuild your containers. This is done
+via the following command::
+
+    docker-compose -f dev.yml build
+
+
+Creating your env file
+^^^^^^^^^^^^^^^^^^^^^^
+
+We provide a working .env.dev configuration file that is suitable for
+development. However, to enable customization on your machine, you should
+also create a .env file that will hold your personal environment
+variables (those will not be commited to the project).
+
+Create it like this::
+
+    touch .env
+
+
+Database management
+^^^^^^^^^^^^^^^^^^^
+
+To setup funkwhale's database schema, run this::
+
+    docker-compose -f dev.yml run --rm api python manage.py migrate
+
+This will create all the tables needed for the API to run proprely.
+You will also need to run this whenever changes are made on the database
+schema.
+
+It is safe to run this command multiple times, so you can run it whenever
+you fetch develop.
+
+
+Development data
+^^^^^^^^^^^^^^^^
+
+You'll need at least an admin user and some artists/tracks/albums to work
+locally.
+
+Create an admin user with the following command::
+
+    docker-compose -f dev.yml run --rm api python manage.py createsuperuser
+
+Injecting fake data is done by running the fllowing script::
+
+    artists=25
+    command="from funkwhale_api.music import fake_data; fake_data.create_data($artists)"
+    echo $command | docker-compose -f dev.yml run --rm api python manage.py shell -i python
+
+The previous command will create 25 artists with random albums, tracks
+and metadata.
+
+
+Launch all services
+^^^^^^^^^^^^^^^^^^^
+
+Then you can run everything with::
+
+    docker-compose -f dev.yml up
+
+This will launch all services, and output the logs in your current terminal window.
+If you prefer to launch them in the background instead, use the ``-d`` flag, and access the logs when you need it via ``docker-compose -f dev.yml logs --tail=50 --follow``.
+
+Once everything is up, you can access the various funkwhale's components:
+
+- The Vue webapp, on http://localhost:8080
+- The API, on http://localhost:8080/api/v1/
+- The django admin, on http://localhost:8080/api/admin/
+
+Stopping everything
+^^^^^^^^^^^^^^^^^^^
+
+Once you're down with your work, you can stop running containers, if any, with::
+
+    docker-compose -f dev.yml stop
+
+
+Removing everything
+^^^^^^^^^^^^^^^^^^^
+
+If you want to wipe your development environment completely (e.g. if you want to start over from scratch), just run::
+
+    docker-compose -f dev.yml down -v
+
+This will wipe your containers and data, so please be careful before running it.
+
+You can keep your data by removing the ``-v`` flag.
+
+
+Working with federation locally
+-------------------------------
+
+This is not needed unless you need to work on federation-related features.
+
+To achieve that, you'll need:
+
+1. to update your dns resolver to resolve all your .dev hostnames locally
+2. a reverse proxy (such as traefik) to catch those .dev requests and
+   and with https certificate
+3. two instances (or more) running locally, following the regular dev setup
+
+Resolve .dev names locally
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If you use dnsmasq, this is as simple as doing::
+
+    echo "address=/test/172.17.0.1" | sudo tee /etc/dnsmasq.d/test.conf
+    sudo systemctl restart dnsmasq
+
+If you use NetworkManager with dnsmasq integration, use this instead::
+
+    echo "address=/test/172.17.0.1" | sudo tee /etc/NetworkManager/dnsmasq.d/test.conf
+    sudo systemctl restart NetworkManager
+
+Add wildcard certificate to the trusted certificates
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Simply copy bundled certificates::
+
+    sudo cp docker/ssl/test.crt /usr/local/share/ca-certificates/
+    sudo update-ca-certificates
+
+This certificate is a wildcard for ``*.funkwhale.test``
+
+Run a reverse proxy for your instances
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+
+Create docker network
+^^^^^^^^^^^^^^^^^^^^
+
+Create the federation network::
+
+    docker network create federation
+
+Launch everything
+^^^^^^^^^^^^^^^^^
+
+Launch the traefik proxy::
+
+    docker-compose -f docker/traefik.yml up -d
+
+Then, in separate terminals, you can setup as many different instances as you
+need::
+
+    export COMPOSE_PROJECT_NAME=node2
+    docker-compose -f dev.yml run --rm api python manage.py migrate
+    docker-compose -f dev.yml run --rm api python manage.py createsuperuser
+    docker-compose -f dev.yml up nginx api front nginx api celeryworker
+
+Note that by default, if you don't export the COMPOSE_PROJECT_NAME,
+we will default to node1 as the name of your instance.
+
+Assuming your project name is ``node1``, your server will be reachable
+at ``https://node1.funkwhale.test/``. Not that you'll have to trust
+the SSL Certificate as it's self signed.
+
+When working on federation with traefik, ensure you have this in your ``env``::
+
+    # This will ensure we don't bind any port on the host, and thus enable
+    # multiple instances of funkwhale to be spawned concurrently.
+    WEBPACK_DEVSERVER_PORT_BINDING=
+    # This disable certificate verification
+    EXTERNAL_REQUESTS_VERIFY_SSL=false
+    # this ensure you don't have incorrect urls pointing to http resources
+    FUNKWHALE_PROTOCOL=https
+
+
+Typical workflow for a contribution
+-----------------------------------
+
+0. Fork the project if you did not already or if you do not have access to the main repository
+1. Checkout the development branch and pull most recent changes: ``git checkout develop && git pull``
+2. If working on an issue, assign yourself to the issue. Otherwise, consider open an issue before starting to work on something, especially for new features.
+3. Create a dedicated branch for your work ``42-awesome-fix``. It is good practice to prefix your branch name with the ID of the issue you are solving.
+4. Work on your stuff
+5. Commit small, atomic changes to make it easier to review your contribution
+6. Add a changelog fragment to summarize your changes: ``echo "Implemented awesome stuff (#42)" > changes/changelog.d/42.feature"``
+7. Push your branch
+8. Create your merge request
+9. Take a step back and enjoy, we're really grateful you did all of this and took the time to contribute!
+
+
+Internationalization
+--------------------
+
+When working on the front-end, any end-user string should be translated
+using either ``<i18next path="yourstring">`` or the ``$t('yourstring')``
+function.
+
+Extraction is done by calling ``yarn run i18n-extract``, which
+will pull all the strings from source files and put them in a PO file.
+
+Contributing to the API
+-----------------------
+
+Project structure
+^^^^^^^^^^^^^^^^^
+
+.. code-block:: shell
+
+    tree api -L 2 -d
+    api
+    ├── config          # configuration directory (settings, urls, wsgi server)
+    │   └── settings    # Django settings files
+    ├── funkwhale_api   # project directory, all funkwhale logic is here
+    ├── requirements    # python requirements files
+    └── tests           # test files, matches the structure of the funkwhale_api directory
+
+.. note::
+
+    Unless trivial, API contributions must include unittests to ensure
+    your fix or feature is working as expected and won't break in the future
+
+Running tests
+^^^^^^^^^^^^^
+
+To run the pytest test suite, use the following command::
+
+    docker-compose -f dev.yml run --rm api pytest
+
+This is regular pytest, so you can use any arguments/options that pytest usually accept::
+
+    # get some help
+    docker-compose -f dev.yml run --rm api pytest -h
+    # Stop on first failure
+    docker-compose -f dev.yml run --rm api pytest -x
+    # Run a specific test file
+    docker-compose -f dev.yml run --rm api pytest tests/test_acoustid.py
+
+Writing tests
+^^^^^^^^^^^^^
+
+Although teaching you how to write unit tests is outside of the scope of this
+document, you'll find below a collection of tips, snippets and resources
+you can use if you want to learn on that subject.
+
+Useful links:
+
+- `A quick introduction to unit test writing with pytest <https://semaphoreci.com/community/tutorials/testing-python-applications-with-pytest>`_
+- `A complete guide to Test-Driven Development (although not using Pytest) <https://www.obeythetestinggoat.com/>`_
+- `pytest <https://docs.pytest.org/en/latest/>`_: documentation of our testing engine and runner
+- `pytest-mock <https://pypi.org/project/pytest-mock/>`_: project page of our mocking engine
+- `factory-boy <http://factoryboy.readthedocs.io/>`_: documentation of factory-boy, which we use to easily generate fake objects and data
+
+Recommendations:
+
+- Test files must target a module and mimic ``funkwhale_api`` directory structure: if you're writing tests for ``funkwhale_api/myapp/views.py``, you should put thoses tests in ``tests/myapp/test_views.py``
+- Tests should be small and test one thing. If you need to test multiple things, write multiple tests.
+
+We provide a lot of utils and fixtures to make the process of writing tests as
+painless as possible. You'll find some usage examples below.
+
+Use factories to create arbitrary objects:
+
+.. code-block:: python
+
+    # funkwhale_api/myapp/users.py
+
+    def downgrade_user(user):
+        """
+        A simple function that remove superuser status from users
+        and return True if user was actually downgraded
+        """
+        downgraded = user.is_superuser
+        user.is_superuser = False
+        user.save()
+        return downgraded
+
+    # tests/myapp/test_users.py
+    from funkwhale_api.myapp import users
+
+    def test_downgrade_superuser(factories):
+        user = factories['users.User'](is_superuser=True)
+        downgraded = users.downgrade_user(user)
+
+        assert downgraded is True
+        assert user.is_superuser is False
+
+    def test_downgrade_normal_user_does_nothing(factories):
+        user = factories['users.User'](is_superuser=False)
+        downgraded = something.downgrade_user(user)
+
+        assert downgraded is False
+        assert user.is_superuser is False
+
+.. note::
+
+    We offer factories for almost if not all models. Factories are located
+    in a ``factories.py`` file inside each app.
+
+Mocking: mocking is the process of faking some logic in our code. This is
+useful when testing components that depend on each other:
+
+.. code-block:: python
+
+    # funkwhale_api/myapp/notifications.py
+
+    def notify(email, message):
+        """
+        A function that sends an email to the given recipient
+        with the given message
+        """
+
+        # our email sending logic here
+        # ...
+
+    # funkwhale_api/myapp/users.py
+    from . import notifications
+
+    def downgrade_user(user):
+        """
+        A simple function that remove superuser status from users
+        and return True if user was actually downgraded
+        """
+        downgraded = user.is_superuser
+        user.is_superuser = False
+        user.save()
+        if downgraded:
+            notifications.notify(user.email, 'You have been downgraded!')
+        return downgraded
+
+    # tests/myapp/test_users.py
+    def test_downgrade_superuser_sends_email(factories, mocker):
+        """
+        Your downgrade logic is already tested, however, we want to ensure
+        an email is sent when user is downgraded, but we don't have any email
+        server available in our testing environment. Thus, we need to mock
+        the email sending process.
+        """
+        mocked_notify = mocker.patch('funkwhale_api.myapp.notifications.notify')
+        user = factories['users.User'](is_superuser=True)
+        users.downgrade_user(user)
+
+        # here, we ensure our notify function was called with proper arguments
+        mocked_notify.assert_called_once_with(user.email, 'You have been downgraded')
+
+
+    def test_downgrade_not_superuser_skips_email(factories, mocker):
+        mocked_notify = mocker.patch('funkwhale_api.myapp.notifications.notify')
+        user = factories['users.User'](is_superuser=True)
+        users.downgrade_user(user)
+
+        # here, we ensure no email was sent
+        mocked_notify.assert_not_called()
+
+Views: you can find some readable views tests in :file:`tests/users/test_views.py`
+
+.. note::
+
+    A complete list of available-fixtures is available by running
+    ``docker-compose -f dev.yml run --rm api pytest --fixtures``
+
+
+Contributing to the front-end
+-----------------------------
+
+Running tests
+^^^^^^^^^^^^^
+
+To run the front-end test suite, use the following command::
+
+    docker-compose -f dev.yml run --rm front yarn run unit
+
+We also support a "watch and test" mode were we continually relaunch
+tests when changes are recorded on the file system::
+
+    docker-compose -f dev.yml run --rm front yarn run unit-watch
+
+The latter is especially useful when you are debugging failing tests.
+
+.. note::
+
+    The front-end test suite coverage is still pretty low
diff --git a/README.rst b/README.rst
index 8a0ea49320bb8f63115219c462bb49f3c4355d99..c8a126451338c32df924e6801accecda3db3fb56 100644
--- a/README.rst
+++ b/README.rst
@@ -15,282 +15,8 @@ We offer various Matrix.org rooms to discuss about funkwhale:
 
 Please join those rooms if you have any questions!
 
-Running the development version
--------------------------------
+Contribute
+----------
 
-If you want to fix a bug or implement a feature, you'll need
-to run a local, development copy of funkwhale.
-
-We provide a docker based development environment, which should
-be both easy to setup and work similarly regardless of your
-development machine setup.
-
-Instructions for bare-metal setup will come in the future (Merge requests
-are welcome).
-
-Installing docker and docker-compose
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-This is already cover in the relevant documentations:
-
-- https://docs.docker.com/install/
-- https://docs.docker.com/compose/install/
-
-Cloning the project
-^^^^^^^^^^^^^^^^^^^
-
-Visit https://code.eliotberriot.com/funkwhale/funkwhale and clone the repository using SSH or HTTPS. Exemple using SSH::
-
-    git clone ssh://git@code.eliotberriot.com:2222/funkwhale/funkwhale.git
-    cd funkwhale
-
-
-A note about branches
-^^^^^^^^^^^^^^^^^^^^^
-
-Next release development occurs on the "develop" branch, and releases are made on the "master" branch. Therefor, when submitting Merge Requests, ensure you are merging on the develop branch.
-
-
-Working with docker
-^^^^^^^^^^^^^^^^^^^
-
-In developpement, we use the docker-compose file named ``dev.yml``, and this is why all our docker-compose commands will look like this::
-
-    docker-compose -f dev.yml logs
-
-If you do not want to add the ``-f dev.yml`` snippet everytime, you can run this command before starting your work::
-
-    export COMPOSE_FILE=dev.yml
-
-
-Building the containers
-^^^^^^^^^^^^^^^^^^^^^^^
-
-On your initial clone, or if there have been some changes in the
-app dependencies, you will have to rebuild your containers. This is done
-via the following command::
-
-    docker-compose -f dev.yml build
-
-
-Creating your env file
-^^^^^^^^^^^^^^^^^^^^^^
-
-We provide a working .env.dev configuration file that is suitable for
-development. However, to enable customization on your machine, you should
-also create a .env file that will hold your personal environment
-variables (those will not be commited to the project).
-
-Create it like this::
-
-    touch .env
-
-
-Database management
-^^^^^^^^^^^^^^^^^^^
-
-To setup funkwhale's database schema, run this::
-
-    docker-compose -f dev.yml run --rm api python manage.py migrate
-
-This will create all the tables needed for the API to run proprely.
-You will also need to run this whenever changes are made on the database
-schema.
-
-It is safe to run this command multiple times, so you can run it whenever
-you fetch develop.
-
-
-Development data
-^^^^^^^^^^^^^^^^
-
-You'll need at least an admin user and some artists/tracks/albums to work
-locally.
-
-Create an admin user with the following command::
-
-    docker-compose -f dev.yml run --rm api python manage.py createsuperuser
-
-Injecting fake data is done by running the fllowing script::
-
-    artists=25
-    command="from funkwhale_api.music import fake_data; fake_data.create_data($artists)"
-    echo $command | docker-compose -f dev.yml run --rm api python manage.py shell -i python
-
-The previous command will create 25 artists with random albums, tracks
-and metadata.
-
-
-Launch all services
-^^^^^^^^^^^^^^^^^^^
-
-Then you can run everything with::
-
-    docker-compose -f dev.yml up
-
-This will launch all services, and output the logs in your current terminal window.
-If you prefer to launch them in the background instead, use the ``-d`` flag, and access the logs when you need it via ``docker-compose -f dev.yml logs --tail=50 --follow``.
-
-Once everything is up, you can access the various funkwhale's components:
-
-- The Vue webapp, on http://localhost:8080
-- The API, on http://localhost:8080/api/v1/
-- The django admin, on http://localhost:8080/api/admin/
-
-
-Running API tests
-^^^^^^^^^^^^^^^^^
-
-To run the pytest test suite, use the following command::
-
-    docker-compose -f dev.yml run --rm api pytest
-
-This is regular pytest, so you can use any arguments/options that pytest usually accept::
-
-    # get some help
-    docker-compose -f dev.yml run --rm api pytest -h
-    # Stop on first failure
-    docker-compose -f dev.yml run --rm api pytest -x
-    # Run a specific test file
-    docker-compose -f dev.yml run --rm api pytest tests/test_acoustid.py
-
-
-Running front-end tests
-^^^^^^^^^^^^^^^^^^^^^^^
-
-To run the front-end test suite, use the following command::
-
-    docker-compose -f dev.yml run --rm front yarn run unit
-
-We also support a "watch and test" mode were we continually relaunch
-tests when changes are recorded on the file system::
-
-    docker-compose -f dev.yml run --rm front yarn run unit-watch
-
-The latter is especially useful when you are debugging failing tests.
-
-.. note::
-
-    The front-end test suite coverage is still pretty low
-
-
-Stopping everything
-^^^^^^^^^^^^^^^^^^^
-
-Once you're down with your work, you can stop running containers, if any, with::
-
-    docker-compose -f dev.yml stop
-
-
-Removing everything
-^^^^^^^^^^^^^^^^^^^
-
-If you want to wipe your development environment completely (e.g. if you want to start over from scratch), just run::
-
-    docker-compose -f dev.yml down -v
-
-This will wipe your containers and data, so please be careful before running it.
-
-You can keep your data by removing the ``-v`` flag.
-
-
-Typical workflow for a merge request
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-0. Fork the project if you did not already or if you do not have access to the main repository
-1. Checkout the development branch and pull most recent changes: ``git checkout develop && git pull``
-2. Create a dedicated branch for your work ``42-awesome-fix``. It is good practice to prefix your branch name with the ID of the issue you are solving.
-3. Work on your stuff
-4. Commit small, atomic changes to make it easier to review your contribution
-5. Add a changelog fragment to summarize your changes: ``echo "Implemented awesome stuff (#42)" > changes/changelog.d/42.feature"``
-6. Push your branch
-7. Create your merge request
-8. Take a step back and enjoy, we're really grateful you did all of this and took the time to contribute!
-
-
-Internationalization
---------------------
-
-When working on the front-end, any end-user string should be translated
-using either ``<i18next path="yourstring">`` or the ``$t('yourstring')``
-function.
-
-Extraction is done by calling ``yarn run i18n-extract``, which
-will pull all the strings from source files and put them in a PO file.
-
-
-Working with federation locally
--------------------------------
-
-To achieve that, you'll need:
-
-1. to update your dns resolver to resolve all your .dev hostnames locally
-2. a reverse proxy (such as traefik) to catch those .dev requests and
-   and with https certificate
-3. two instances (or more) running locally, following the regular dev setup
-
-Resolve .dev names locally
-^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-If you use dnsmasq, this is as simple as doing::
-
-    echo "address=/test/172.17.0.1" | sudo tee /etc/dnsmasq.d/test.conf
-    sudo systemctl restart dnsmasq
-
-If you use NetworkManager with dnsmasq integration, use this instead::
-
-    echo "address=/test/172.17.0.1" | sudo tee /etc/NetworkManager/dnsmasq.d/test.conf
-    sudo systemctl restart NetworkManager
-
-Add wildcard certificate to the trusted certificates
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Simply copy bundled certificates::
-
-    sudo cp docker/ssl/test.crt /usr/local/share/ca-certificates/
-    sudo update-ca-certificates
-
-This certificate is a wildcard for ``*.funkwhale.test``
-
-Run a reverse proxy for your instances
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-
-Create docker network
-^^^^^^^^^^^^^^^^^^^^
-
-Create the federation network::
-
-    docker network create federation
-
-Launch everything
-^^^^^^^^^^^^^^^^^
-
-Launch the traefik proxy::
-
-    docker-compose -f docker/traefik.yml up -d
-
-Then, in separate terminals, you can setup as many different instances as you
-need::
-
-    export COMPOSE_PROJECT_NAME=node2
-    docker-compose -f dev.yml run --rm api python manage.py migrate
-    docker-compose -f dev.yml run --rm api python manage.py createsuperuser
-    docker-compose -f dev.yml up nginx api front nginx api celeryworker
-
-Note that by default, if you don't export the COMPOSE_PROJECT_NAME,
-we will default to node1 as the name of your instance.
-
-Assuming your project name is ``node1``, your server will be reachable
-at ``https://node1.funkwhale.test/``. Not that you'll have to trust
-the SSL Certificate as it's self signed.
-
-When working on federation with traefik, ensure you have this in your ``env``::
-
-    # This will ensure we don't bind any port on the host, and thus enable
-    # multiple instances of funkwhale to be spawned concurrently.
-    WEBPACK_DEVSERVER_PORT_BINDING=
-    # This disable certificate verification
-    EXTERNAL_REQUESTS_VERIFY_SSL=false
-    # this ensure you don't have incorrect urls pointing to http resources
-    FUNKWHALE_PROTOCOL=https
+Contribution guidelines as well as development installation instructions
+are outlined in `CONTRIBUTING <CONTRIBUTING>`_
diff --git a/api/tests/conftest.py b/api/tests/conftest.py
index 64dc394e7eef40663bba19b26bd6896f5a5088b1..51a1bc4c7455c0be929ba503b971b3ec2d8ad244 100644
--- a/api/tests/conftest.py
+++ b/api/tests/conftest.py
@@ -27,12 +27,19 @@ def factories_autodiscover():
 
 @pytest.fixture(autouse=True)
 def cache():
+    """
+    Returns a django Cache instance for cache-related operations
+    """
     yield django_cache
     django_cache.clear()
 
 
 @pytest.fixture
 def factories(db):
+    """
+    Returns a dictionnary containing all registered factories with keys such as
+    users.User or music.Track
+    """
     from funkwhale_api import factories
     for v in factories.registry.values():
         try:
@@ -45,6 +52,10 @@ def factories(db):
 
 @pytest.fixture
 def nodb_factories():
+    """
+    Returns a dictionnary containing all registered factories with a build strategy
+    that does not require access to the database
+    """
     from funkwhale_api import factories
     for v in factories.registry.values():
         try:
@@ -57,6 +68,9 @@ def nodb_factories():
 
 @pytest.fixture
 def preferences(db, cache):
+    """
+    return a dynamic_preferences manager for global_preferences
+    """
     manager = global_preferences_registry.manager()
     manager.all()
     yield manager
@@ -64,6 +78,10 @@ def preferences(db, cache):
 
 @pytest.fixture
 def tmpdir():
+    """
+    Returns a temporary directory path where you can write things during your
+    test
+    """
     d = tempfile.mkdtemp()
     yield d
     shutil.rmtree(d)
@@ -71,11 +89,18 @@ def tmpdir():
 
 @pytest.fixture
 def tmpfile():
+    """
+    Returns a temporary file where you can write things during your test
+    """
     yield tempfile.NamedTemporaryFile()
 
 
 @pytest.fixture
 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)
@@ -85,16 +110,24 @@ def logged_in_client(db, factories, client):
 
 @pytest.fixture
 def anonymous_user():
+    """Returns a AnonymousUser() instance"""
     return AnonymousUser()
 
 
 @pytest.fixture
 def api_client(client):
+    """
+    Return an API client without any authentication
+    """
     return APIClient()
 
 
 @pytest.fixture
 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')
     setattr(api_client, 'user', user)
@@ -104,6 +137,10 @@ def logged_in_api_client(db, factories, api_client):
 
 @pytest.fixture
 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)
@@ -113,6 +150,10 @@ def superuser_api_client(db, factories, api_client):
 
 @pytest.fixture
 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)
@@ -122,11 +163,17 @@ def superuser_client(db, factories, client):
 
 @pytest.fixture
 def api_request():
+    """
+    Returns a dummy API request object you can pass to API views
+    """
     return APIRequestFactory()
 
 
 @pytest.fixture
 def fake_request():
+    """
+    Returns a dummy, non-API request object you can pass to regular views
+    """
     return client.RequestFactory()
 
 
@@ -140,16 +187,6 @@ def activity_registry():
         record.registry[key] = value
 
 
-@pytest.fixture
-def activity_registry():
-    r = record.registry
-    state = list(record.registry.items())
-    yield record.registry
-    record.registry.clear()
-    for key, value in state:
-        record.registry[key] = value
-
-
 @pytest.fixture
 def activity_muted(activity_registry, mocker):
     yield mocker.patch.object(record, 'send')
@@ -157,6 +194,9 @@ def activity_muted(activity_registry, mocker):
 
 @pytest.fixture(autouse=True)
 def media_root(settings):
+    """
+    Sets settings.MEDIA_ROOT to a temporary path and returns this path
+    """
     tmp_dir = tempfile.mkdtemp()
     settings.MEDIA_ROOT = tmp_dir
     yield settings.MEDIA_ROOT
@@ -165,12 +205,19 @@ def media_root(settings):
 
 @pytest.fixture
 def r_mock():
+    """
+    Returns a requests_mock.mock() object you can use to mock HTTP calls made
+    using python-requests
+    """
     with requests_mock.mock() as m:
         yield m
 
 
 @pytest.fixture
 def authenticated_actor(factories, mocker):
+    """
+    Returns an authenticated ActivityPub actor
+    """
     actor = factories['federation.Actor']()
     mocker.patch(
         'funkwhale_api.federation.authentication.SignatureAuthentication.authenticate_actor',
diff --git a/api/tests/music/conftest.py b/api/tests/music/conftest.py
index 1d0fa4e38627ad7f0711082a0edd2c726f02b2e6..4eea8effe173ee5b90bb18dd1400b5dfff39588a 100644
--- a/api/tests/music/conftest.py
+++ b/api/tests/music/conftest.py
@@ -508,21 +508,25 @@ _works['get']['chop_suey'] = {'work': {'id': 'e2ecabc4-1b9d-30b2-8f30-3596ec423d
 
 @pytest.fixture()
 def artists():
+    """Artists as they would be returned by the Musicbrainz API"""
     return _artists
 
 
 @pytest.fixture()
 def albums():
+    """Releases as they would be returned by the Musicbrainz API"""
     return _albums
 
 
 @pytest.fixture()
 def tracks():
+    """Recordings as they would be returned by the Musicbrainz API"""
     return _tracks
 
 
 @pytest.fixture()
 def works():
+    """Works as they would be returned by the Musicbrainz API"""
     return _works
 
 
@@ -563,4 +567,7 @@ def lyricswiki_content():
 
 @pytest.fixture()
 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'
diff --git a/docs/contributing.rst b/docs/contributing.rst
new file mode 100644
index 0000000000000000000000000000000000000000..0e5a08ecf3ce63619bc358bebbc98a8af2ea6331
--- /dev/null
+++ b/docs/contributing.rst
@@ -0,0 +1 @@
+.. include:: ../CONTRIBUTING
diff --git a/docs/index.rst b/docs/index.rst
index e4f0e4422e90ce27adfb13044ec6ba936b413788..481690b708fd49d98d79bdf4b4354d1963339935 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -19,6 +19,7 @@ Funkwhale is a self-hosted, modern free and open-source music server, heavily in
    api
    upgrading
    third-party
+   contributing
    changelog
 
 Indices and tables