Formater automatiquement les migrations Django avec black

Ami·e qui écris du code avec le framework Django, peut-être t'es-tu mis à utiliser le fantastique formateur de code black qui permet de se libérer les neurones d'un peu de bikesheding ?

Ton éditeur est bien configuré pour reformater ton code avec black automatiquement… Oui mais voilà… Quand tu génères tes migrations de base de donnée avec ./manage.py makemigrations, tu dois repasser derrière pour formater le fichier .py de migration généré 😭.

Voici une solution pour que makemigrations produise une migration correctement formatée au sens de black.

Alors déjà, si tu utilises Django en version 4.1 ou supérieure, il n'y a rien à faire, c'est inclus. Bonne journée 😘…

Dans le cas contraire, la bricole suivante permet de faire passer black automatiquement sur les migrations créées.

Surcharger la commande ./manage.py makemigrations

On va étendre le comportement de la commande makemigrations pour qu'elle inclue le reformatage.

Crée un fichier makemigrations.py à l'endroit suivant de ton projet Django (imaginons que le projet se nomme « monprojet ») :

monprojet/
├── management
│   ├── commands
│   │   ├── __init__.py
│   │   └── makemigrations.py
│   └── __init__.py

[…]

(NB: il faut créer les dossiers management, commands et les fichiers (vides) _init_.py si ils n'existent pas déjà).

Détail : on est en train de créer une commande django-admin, et comme elle possède le même nom que la commande makemigrations inclue avec Django, la notre va la « remplacer ».

… Avec le contenu suivant :

import subprocess

from django.core.management.commands.makemigrations import (
    Command as OriginalMakeMigrationsCommand,
)


def black(filepath):
    # assume black is installed and in PATH, will fail on exception otherwise…
    subprocess.check_call(["black", filepath, "-q"])


class Command(OriginalMakeMigrationsCommand):
    def write_migration_files(self, changes):
        ret = super(Command, self).write_migration_files(changes)
        for app_name, migrations in changes.items():
            for migration in migrations:
                black(f"{app_name}/migrations/{migration.name}.py")

        return ret

Détail : on se contente d'hériter le fonctionnement de la commande originale en lui adjoignant une exécution de black par fichier de migration généré.

Il suffit ensuite d'utiliser ./manage.py makemigrations comme d'habitude, joie 😎.


  1. par exemple moi j'utilise uniquement les LTS, donc à la date de rédaction de cette article, la 3.2