Un guide rapide pour django-south

Publié le 05 novembre 2010 par Luc

Une des faiblesses de Django est de ne pas intégrer de mécanisme de migration de base de données. Modifier un modèle peut devenir un casse-tête au moment du déploiement. Heureusement, une des grandes forces de Django est son architecture modulaire qui permet d'étendre les fonctionnalités facilement en intégrant des applications.
South est une application qui permet de faire des migrations de schémas et de données pour une base de données. Sur son blog, Jacob Kaplan-Moss, un des créateurs de Django, cite South comme l'application préférée après un sondage Twitter auprès des djangonautes . Je n'ai pas participé à ce sondage mais je crois que c'est aussi mon choix.
South s'accorde merveilleusement avec Django tout comme les jazzmen Django Reinhardt et Eddie South (d'où, j'imagine, vient le nom de cette application) sur cette improvisation autour  du concerto en ré mineur de Jean-Sébastien Bach. Lecteurs mélomanes, cela vaut le détour : http://www.youtube.com/watch?v=gQZw3nema0Q

Je vous propose aujourd'hui un guide rapide d'utilisation de South qui veut simplement partager mon expérience d'utilisateur. N'hésitez pas à commenter l'article pour corriger ou préciser un élément.

Installation

Après avoir installé django-south depuis pip, easy-install ou autres, il suffit d'ajouter 'south' à la liste des INSTALLED_APPS.

manage.py schemamigration app --initial

Pour permettre de migrer les modèles, South prend des "photos" à différents instants du modèle et est capable de définir quelles sont les différences à appliquer entre 2 photos.
La 1ère étape est de prendre une photo initiale par la commande ci-dessus. Un répertoire migration est créé ainsi qu'un __init__.py (c'est un module Python) et un fichier 0001_initial.py qui contient la version initiale du modèle de données et les commandes pour le créer (forward) et le supprimer (backward)

manage.py syncdb

La commande de synchronisation de base de Django est modifiée et ne gère plus la création des tables de l'application "southifiée".

manage.py migrate app

La commande migrate va charger les migrations à appliquer. Elles correspondent à autant de fichiers python dont le nom commence par un numéro de 4 chiffres et qui indique dans quel ordre les appliquer. Cette commande va donc charger notre 0001_initial.py et exécuter sa méthode forward pour créer en base les tables correspondantes.

manage.py migrate app --fake

Peut-être votre base est-elle déjà créée au moment où vous intégrez South. La commande migrate va donc échouer en essayant de récréer des tables existantes. Pour détecter quelles migrations doivent être appliquées, South se base sur une table d'historique dans laquelle sont stockées les migrations effectuées pour chaque application. Pour éviter qu'il recrée la base, il faut donc lui dire que cette migration a déjà été appliquée en insérant un enregistrement en base. Inutile de bidouiller une requête SQL, South propose l'option fake pour la commande migrate. Ainsi la migration est marquée comme effectuée sans que le code correspondant n'ait été exécuté.

manage.py schemamigration app label --auto

On peut ensuite modifier le modèle ajouter/modifier/supprimer des champs ou des classes au modèle Django. Pour permettre à South de générer la migration correspondante, on prend une nouvelle photo du modèle en exécutant la commande ci-dessus. Le label est un champ libre qui vous permettra de repérer ce que fait la migration. L'option --auto génère automatiquement le code nécessaire pour appliquer (forward) ou défaire (backward) la migration. Je crois que l'on peut se passer de --auto et écrire ce code soi-même mais jusqu'à présent --auto a toujours parfaitement fonctionner pour moi et puis rien n'empêche d'adapter le code ainsi généré.

Les valeurs par défaut

South (ou plutôt Django bien souvent) détecte si votre modèle a des erreurs et si c'est le cas la création de la migration échouera. Mais South détecte aussi intelligemment des problèmes potentiels lors de l'application de la migration comme, par exemple, un oubli de valeur par défaut pour la création d'une nouvelle colonne. Dans ce cas, South vous demandera votre choix: aller corriger votre modèle en donnant une valeur par défaut pour cette nouvelle colonne ou bien saisir une valeur qui sera utilisée pour les enregistrements déjà existants.

manage.py datamigration app label

Jusque là nous avons appliqué des migrations sur le schéma de la base de données. Mais il est aussi possible de migrer des données  de la base. South demande à ce que ces 2 types de migration soient bien isolées. La commande ci-dessus génère un squelette vide dans lequel on utilisera l'API de la base de données de Django.

Utiliser l'orm

Il faut donc implémenter le code de la fonction forward. Prenons un exemple, on a ajouté dans une migration précédente un champ  email à un modèle Company et on veut initialiser la valeur de ce champ pour les objets existants avec une valeur contact@<nom>.fr. Voici le code correspondant:
def forward(orm):
   for c in orm.Company.objects.all():
   c.email = 'contact@{0}.fr'.format(c.name.lower())
   c.save()

Vous noterez peut-être que l'accès au modèle se fait via l'argument orm et non pas via un "from models import Company". En effet, l'orm permet de travailler avec la version du modèle correspondant à celle de la migration. Le "from models import Company" va charger la dernière version du modèle qui ne correspondra pas forcément à celle au moment de la migration. Imaginons que nous décidions à l'avenir de supprimer le champ email, notre migration ne fonctionnerait pas. Il ne faut donc pas faire d'"import models" dans une migration mais y accéder uniquement via le paramètre orm des méthodes forward et backward.

manage.py datamigration app label --freeze auth

Par défaut, la variable orm ne permet d'accéder qu'aux classes du modèle de l'application de la migration. Ceci est un problème si la migration de données nécessite d'autres applications. Il est toutefois possible de forcer South à inclure d'autres applications dans la photo du modèle via la commande ci-dessus. Dans cet exemple, les classes de l'application auth seront accessibles dans le script via la syntaxe suivante: orm['auth.Group'].

manage.py migrate app 0005

Il est possible de revenir en arrière sur une migration en stipulant simplement le numéro de révision vers laquelle on souhaite aller. Si le numéro actuel est supérieur au numéro donné, les migrations par backward sont effectuées. Il est parfois bien pratique de pouvoir faire et défaire la base...

South et les tests unitaires

South a parfois un comportement inattendu avec les tests unitaires. Sur une application, j'ai eu, par exemple, des tests en erreur avec sqlite car ce moteur de base de données ne sait pas changer la contrainte d'unicité sur une colonne. Pour éviter d'avoir les tests pollués par South, il est possible de désactiver la migration lors des tests en ajoutant dans le settings.py SOUTH_TESTS_MIGRATE = False. La base de test sera ainsi construite avec syncdb traditionnel.

Certaines migrations de données peuvent toutefois être nécessaires pour le bon déroulement des tests. Je n'ai pour l'instant pas trouvé de meilleure solution que d'isoler ces migrations dans un module spécifique et de les appeler dans le setUp du TestCase en simulant la variable orm.

South et les champs personnalisés

Un problème peut se poser si l'on utilise dans son modèle des champs personnalisés (par exemple un champ hérité de CharField et qui vérifie que le texte suit une expression régulière donnée). Dans ce cas, je vous invite à vous reporter à cet article qui traite ce problème.

Voila pour ce guide rapide qui j'espère vous aidera à utiliser cette application que je trouve essentielle pour un site Django. Merci de vos commentaires si vous trouvez des erreurs ou si vous avez des précisions à ajouter.