Ecrire une commande Django


Quand vous avez à faire un script pour un projet Django, il est pratique de l’avoir sous forme de sous-commande de manage.py : c’est portable d’un projet à l’autre et ça permet d’utiliser l’ORM et les templates sans réglage puisqu’on a accès à tous les settings, automatiquement.

Malheureusement le wrapper de Django destiné à cela date un peu, il est plutôt lourd, pas très souple et utilise des pratiques qui ne sont plus au goût du jour depuis quelques années. Rien de rédhibitoire, mais tout de même.

D’abord, il faut placer sa commande dans un fichier portant le nom de la commande, et dans un package management.commands d’une de vos apps.

Par exemple, si vous voulez faire une commande “nettoyer_godmichets” dans l’application “sex_shop”, il faudra la mettre bien profondément dans le tréfonds de votre projet :

racine
│   ├── sex_shop
│   │   ├── management
│   │   │   ├── commands
│   │   │   │   ├── nettoyer_godmichets.py
│   │   │   │   ├── __init__.py
│   │   │   │   └── __init__.pyc
│   │   │   ├── __init__.py

Notez les fichiers __init__.py, indispendables sinon votre commande ne sera pas trouvée. Oh, et ‘sex_shop’ doit être dans INSTALLED_APPS.

Ensuite, votre commande doit être une classe héritant de BaseCommand. Sa méthode handle() sera appelée automatiquement au lancement de la commande.

from django.core.management.base import BaseCommand
from sex_shop.models import Godmichet
 
class Command(BaseCommand):
 
    def handle(self, *args, **options):
        for god in Godmichet.objects.all():
            god.clean()

Et vous pouvez lancer la commande :

./manage.py nettoyer_godmichets

Néanmoins généralement on voudra avoir un peu de décorum.

 
from optparse import make_option
 
from django.core.management.base import BaseCommand
from sex_shop.models import Godmichet, Marque
 
 
class Command(BaseCommand):
 
    # ici on peut mettre un message d'aide
    help = 'Fait briller dard dard les dards'
 
    # optionellement une aide pour les arguments
    args = 'marque1, [marque2], marque3'
 
    # On peut ajouter des options passables à la commande
    option_list = BaseCommand.option_list + (
        make_option('--dry-run',
            action='store_true',
            dest='dry_run',
            default=False,
            help='Affichage uniquement, aucune action réelle'),
        )
 
    def handle(self, *args, **options):
 
        # on retrouve dans args les arguments positionnels de la commande
 
        if not args:
            for god in Godmichet.objects.all():
                # Plutôt que print(), on peut utiliser ce wrapper
                # pour ecrire sur le terminal. Cela permet de bénéficier
                # automatiquement de l'option --verbosity
                self.stdout.write('Processing %s' % god.name)
 
                # La valeur des options est passée via kwargs.
                if not options['dry_run']:
                    god.clean()
        else:
            # l'utilisateur a passé des marques ? On nettoie que les gods
            # de ces marques...
            for marque in args:
                try:
                    gods = Marque.objects.get(name=marque).gods.all()
                    for god in gods:
                        self.stdout.write('Processing %s' % god.name)
 
                        if not options['dry_run']:
                            god.clean()
 
                except Marque.DoesNotExist:
                    # si la marque n'existe pas, on fait une erreur
                    # ceci arrête le script, retourne un code d'erreur 1
                    # et met le texte en rouge
                    raise CommandError('La marque %s n'existe pas' %s marque)

Et voilà, votre commande accepte maintenant optionnellement qu’on lui passe une liste de marques et/ou une option --dry-run:

./manage.py nettoyer_godmichet devilmaycry choucroutebestfriend --dry-run

En plus de ça, la commande accepte automatiquement :

  • --help
  • --verbosity
  • --settings
  • --pythonpath

Qui ont le même effet que sur toutes les commandes django officielles.

5 thoughts on “Ecrire une commande Django

  • foxmask

    j’en cherchais une de commande pile poil hier pour flush la base de données (celle là meme qui est lancée quand on demarre les test unitaires). ya pas le les smartphones sur la meme longueur d’ondes ;)

  • Sam Post author

    Moi je me suis fais une commande fabric pour ça. Faudra que je poste mes recettes fabric un de ces 4.

  • Metikha

    Super, merci pour l’article !

    (Petite erreur dans le code, handle prend **options et vous utilisez **kwargs dans la fonction)
    Bonne continuation !

  • ZuluPro

    J’ajouterai ce petit snippet dans /bin pour ne plus avoir à aller chercher manage.py:

    #!/usr/bin/env python
    # Shortcut to manage.py
     
    import sys
    from os import environ
    from os.path import dirname, abspath
    from django.core.management import execute_from_command_line 
    from my_app import my_app
     
     
    # Find where is your app
    APP_DIR = dirname(dirname(abspath(my_app.__file__)))
    # Set env to use you app
    sys.path.append(APP_DIR)
    environ['DJANGO_SETTINGS_MODULE'] = 'my_app.settings'
    execute_from_command_line()

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.