Przejdź do treści

Django – manage.py i niestandardowe polecenia

Czasami potrzebujemy wykonać jakieś proste, powtarzalne czynności, które można łatwo zautomatyzować. Django posiada mechanizm, który umożliwia dodawanie nowych komend uruchamianych przez manage.py. Wywołanie komendy będzie miało postać:

./manage.py customcommand --with-parameter

Dodatkowo możemy w ten sposób zaprogramować jakieś zadania uruchamiane cyklicznie poprzez crona. Dzięki temu nie musimy wywoływać adresu url, tylko uruchamiamy polecenie z linii komend.

Pierwsza komenda

Najprostszym przykładem komendy będzie wypisanie wiadomości na konsolkę. Dla przykładu niech projekt nazywa się sample a moduł blog. W katalogu sample/blog/management/commands tworzymy plik blog.py. Nazwa pliku jest nazwą komendy. Plik będzie posiadał klasę Command dziedziczącą po BaseCommand. Metoda handle będzie odpowiedzialna za obsłużenie wywołanej komendy. Przykładowy plik wygląda następująco:

from django.core.management import BaseCommand
class Command(BaseCommand):
def handle(self, *args, **options):
self.stdout.write("Basic command")
view raw base-command.py hosted with ❤ by GitHub

Użycie self.stdout.write ułatwi późniejsze testowanie komendy, które jest opisane w dalszej części artykułu

Aby sprawdzić poprawność działania komendy, wywołujemy z linii komend ./manage.py blog. W wyniku na konsoli powinniśmy ujrzeć napis: Basic command.

Przekazywanie argumentów

Każda komenda może przyjmować argumenty, które będą modyfikować jej działanie. Załóżmy, że chcemy mieć kontrolę nad tym czy zostanie wypisany dodatkowy tekst oraz ile razy zostanie on wypisany. Pierwszym krokiem będzie dodanie parametru mówiącego o tym czy wyświetlić dodatkowy tekst. Do klasy należy dodać metodę add_arguments, która definiuje jakie argumenty będzie przyjmować komenda. Dla przykładowego argumentu powinna ona wyglądać następująco:

def add_arguments(self, parser):
parser.add_argument('-d', '--display',
action='store_true',
default=False,
dest='show',
help='display text')
view raw args.py hosted with ❤ by GitHub

Pierwszy argument definiuje skróconą wersję parametru, kolejny długą. Możemy więc dodać parametr na dwa sposoby:

./manage.py blog -d
#- lub -
./manage.py blog --display

Kolejny parametr definiuje jaka akcja zostanie wykonana jeśli ten parametr się pojawi. Domyślnie wartość jest po prostu zapisywana w pamięci, jednak w tym przypadku chcielibyśmy otrzymać wartość True jeśli parametr się pojawi oraz False jeśli go nie ma. Akcja store_true powoduje zapisanie True jeśli parametr się pojawi (analogicznie istnieje akcja store_false) a wartość domyślna (default=False) powoduje zapisanie False do parametru jeśli go nie ma. Parametr dest definiuje nazwę opcji, pod którą będzie zapisana wartość. Ostatni parametr, help definiuje opis parametru.

Po dodaniu argumentu komendy możemy przejść do użycia go w metodzie handle. Należy zmodyfikować metodę do następującej postaci:

def handle(self, *args, **options):
print("Basic command")
if options['show']:
self.stdout.write("Optional text")
view raw handle.py hosted with ❤ by GitHub

Dodane zostały linie 4 i 5, w których testujemy opcję show. W przypadku gdy show jest prawdą (pojawił się parametr -d lub –display) wyświetlany jest tekst.

Mając cały kod gotowy, możemy uruchomić komendę i sprawdzić, czy działa poprawnie.

Kolejnym krokiem będzie dodanie opcji pozwalającej zdefiniować, ile razy ma zostać wyświetlona linijka tekstu. Do metody add_arguments należy dodać następujący kod:

parser.add_argument("-r", "--repeat", help="how many times repeat text", default=1, type=int)

Parametry są analogiczne jak w poprzednim przykładzie, z tą różnicą, że zostawiamy domyślną akcję, oraz definiujemy typ parametru na liczbę całkowitą (type=int).

Metodę handle modyfikujemy poprzez dodanie pętli:

def handle(self, *args, **options):
print("Basic command")
if options['show']:
for i in range(0, options['repeat']):
self.stdout.write("Optional text")
view raw handle2.py hosted with ❤ by GitHub

Wykonanie polecenia:

./manage.py blog -d -r 3

Spowoduje wyświetlenie tekstu:

Basic command
Optional text
Optional text
Optional text

Tekst pomocy

W przypadku gdy uruchomimy skrypt manage.py bez argumentów, wyświetlona zostanie lista dostępnych akcji. Dodana przez nas akcja również będzie uwzględniona na wydruku. Uruchomienie manage.py help blog wyświetli listę argumentów wraz z opisem. Na liście pojawią się wszystkie argumenty przyjmowane przez Django, takie jak settings, version, oraz zdefiniowane przez nas wraz z opisem podanym w parametrze help. Przykładowy wydruk z terminala:

./manage.py help blog
[DEBUG] 2016-01-10 18:16:28,293: Initialization
usage: manage.py blog [-h] [--version] [-v {0,1,2,3}] [--settings SETTINGS]
                      [--pythonpath PYTHONPATH] [--traceback] [--no-color]
                      [-r REPEAT] [-d]

optional arguments:
  -h, --help            show this help message and exit
  --version             show program's version number and exit
  -v {0,1,2,3}, --verbosity {0,1,2,3}
                        Verbosity level; 0=minimal output, 1=normal output,
                        2=verbose output, 3=very verbose output
  --settings SETTINGS   The Python path to a settings module, e.g.
                        "myproject.settings.main". If this isn't provided, the
                        DJANGO_SETTINGS_MODULE environment variable will be
                        used.
  --pythonpath PYTHONPATH
                        A directory to add to the Python path, e.g.
                        "/home/djangoprojects/myproject".
  --traceback           Raise on CommandError exceptions
  --no-color            Don't colorize the command output.
  -r REPEAT, --repeat REPEAT
                        how many times repeat text
  -d, --display         display text

Testowanie komendy

Do stworzonej komendy powinniśmy dodać testy, aby upewnić się, że działa ona prawidłowo. Zacznijmy od podstawowego testu komendy bez parametrów. Klasa testowa będzie wyglądać następująco:

from django.core.management import call_command
from django.test import TestCase
from django.utils.six import StringIO
class BlogCommandTest(TestCase):
def test_basic_command_output(self):
out = StringIO()
call_command('blog', stdout=out)
self.assertIn('Basic command\n', out.getvalue())
view raw test.py hosted with ❤ by GitHub

Uruchomienie testu powinno pokazać, że komenda działa prawidłowo. Możemy dodać jeszcze kilka testów sprawdzających zachowanie komendy z parametrami:

def test_command_with_display_parameter(self):
out = StringIO()
call_command('blog', '--display', stdout=out)
self.assertEquals('Basic command\nOptional text\n', out.getvalue())
def test_command_with_display_and_repeatparameter(self):
out = StringIO()
call_command('blog', '--display', '-r 3', stdout=out)
self.assertEquals('Basic command\nOptional text\nOptional text\nOptional text\n', out.getvalue())
view raw test2.py hosted with ❤ by GitHub

Podsumowanie

Mechanizm komend pozwala zautomatyzować powtarzalne czynności. Czas poświęcony na implementację mechanizmu komend, pozwala zaoszczędzić dużo czasu przy każdym późniejszym uruchomieniu komendy. Więcej na temat dodawania własnych komend można przeczytać w dokumentacji Django pod adresem: https://docs.djangoproject.com/en/1.9/howto/custom-management-commands/. Dokumentacja parametrów parsowania argumentów znajduje się pod adresem: https://docs.python.org/3/library/optparse.html#module-optparse

Pełny kod klasy obsługującej komendę

from django.core.management import BaseCommand
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument("-r", "--repeat", help="how many times repeat text", default=1, type=int)
parser.add_argument('-d', '--display',
action='store_true',
dest='show',
default=False,
help='display text')
def handle(self, *args, **options):
self.stdout.write("Basic command")
if options['show']:
for i in range(0, options['repeat']):
self.stdout.write("Optional text")
view raw command.py hosted with ❤ by GitHub

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

%d