Создание пользовательских команд управления в Django
Django распространяется с различными утилитами (командами), выполняемыми в командной строке, которые вызываются с помощью скрипта django-admin.py
или manage.py
(Custom Django Management Commands). Хорошая вещь в том, что вы также можете добавить свои собственные команды. Они могут хорошо помочь, когда необходимо взаимодействовать с приложением из командной строки терминала, а также их можно использовать в cron
— утилите, использующейся для периодического выполнения заданий в определённое время.
Вступление
Перед тем, как начнем писать свои команды, ознакомимся с интерфейсом команднйо строки Django. Как минимум, при начале разработки в Django, вы должны были ознакомиться с такими командами, как startproject
, runserver
и collectstatic
. Полный перечень встроенных команд можно узнать из справки:
python manage.py help
Результат будет примерно таким:
Type 'manage.py help <subcommand>' for help on a specific subcommand. Available subcommands: [auth] changepassword createsuperuser [contenttypes] remove_stale_contenttypes [django] check compilemessages createcachetable dbshell diffsettings dumpdata flush inspectdb loaddata makemessages makemigrations migrate sendtestemail shell showmigrations sqlflush sqlmigrate sqlsequencereset squashmigrations startapp startproject test testserver [sessions] clearsessions [staticfiles] collectstatic findstatic runserver
Свои команды создаются в приложениях: создается каталог management, с вложенным каталогом commands. Пример:
mysite/ <-- каталог проекта |-- core/ <-- каталог приложения | |-- management/ | | +-- commands/ | | +-- my_custom_command.py <-- модуль с кодом команды | |-- migrations/ | | +-- __init__.py | |-- __init__.py | |-- admin.py | |-- apps.py | |-- models.py | |-- tests.py | +-- views.py |-- mysite/ | |-- __init__.py | |-- settings.py | |-- urls.py | |-- wsgi.py +-- manage.py
В качестве названия команды, вызываемой в команднйо строке используется имя файла. В нашем примере файл my_custom_command.py
, так что команда будет вызываться следующим образом:
python manage.py my_custom_command
Основной пример
Создадим базовый пример своей команды:
management/commands/what_time_is_it.py
from django.core.management.base import BaseCommand
from django.utils import timezone
class Command(BaseCommand):
help = 'Displays current time'
def handle(self, *args, **kwargs):
time = timezone.now().strftime('%X')
self.stdout.write("It's now %s" % time)
В основном команда управления Django состоит из класса с именем Command
, который наследуется от BaseCommand
. Код команды должен быть определен внутри метода handle()
.
Посмотрите, мы назвали наш модуль what_time_is_it.py
. Эта команда может быть выполнена как:
python manage.py what_time_is_it
Вывод исполнения команды:
It's now 18:35:31
Возникает вопрос, чем это отличается от обычного скрипта Python или в чем его польза. Наилучшим преимуществом является то, что все возможности Django доступны и готовы к использованию, что можно импортировать модели, выполнять запросы в базу данных с помощью ORM Django и взаимодействовать со всеми ресурсами проекта.
Обработка аргументов
Для обработки аргументов команды Django использует argparse, которая является частью стандартной библиотеки Python. В своей команде мы должны использовать метод add_arguments
.
Позиционные аргументы
Следующий пример — это команда, создающая случайных пользователей. Она принимает обязательный аргумент total
, который определяет, сколько пользователей должно быть создано этой командой.
management/commands/create_users.py
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from django.utils.crypto import get_random_string
class Command(BaseCommand):
help = u'Создание случайного пользователя'
def add_arguments(self, parser):
parser.add_argument('total', type=int, help=u'Количество создаваемых пользователей')
def handle(self, *args, **kwargs):
total = kwargs['total']
for i in range(total):
User.objects.create_user(username=get_random_string(), email='', password='123')
Использование этой команды:
python manage.py create_users 10
Опциональные аргументы
Опциональные (и именованные) аргументы могут передаваться в любом порядке. В следующем примере находится определение аргумента prefix
, который будет скомпонован с полем username
.
management/commands/create_users.py
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from django.utils.crypto import get_random_string
class Command(BaseCommand):
help = 'Создание случайного пользователя'
def add_arguments(self, parser):
parser.add_argument('total', type=int, help='Количество создаваемых пользователей')
# Optional argument
parser.add_argument('-p', '--prefix', type=str, help='Префикс для username', )
def handle(self, *args, **kwargs):
total = kwargs['total']
prefix = kwargs['prefix']
for i in range(total):
if prefix:
username = '{prefix}_{random_string}'.format(prefix=prefix, random_string=get_random_string())
else:
username = get_random_string()
User.objects.create_user(username=username, email='', password='123')
Использование:
python manage.py create_users 10 --prefix custom_user
или
python manage.py create_users 10 -p custom_user
Если указан префикс, то поле username будет, например, таким: custom_user_oYwoxtt4vNHR
. Без указания префикса результат будет следующим: oYwoxtt4vNHR
, т. е. просто случайная строка.
Флаговые аргументы
Один из типов необязательных аргументов — это флаг, который использует булевый тип. Давайте добавим флаг — admin
, для указания команде создать суперпользователя или обычного пользователя, если флаг не указан.
management/commands/create_users.py
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
from django.utils.crypto import get_random_string
class Command(BaseCommand):
help = 'Create random users'
def add_arguments(self, parser):
parser.add_argument('total', type=int, help='Количество создаваемых пользователей')
parser.add_argument('-p', '--prefix', type=str, help='Префикс для username')
parser.add_argument('-a', '--admin', action='store_true', help='Создание учетной записи администратора')
def handle(self, *args, **kwargs):
total = kwargs['total']
prefix = kwargs['prefix']
admin = kwargs['admin']
for i in range(total):
if prefix:
username = '{prefix}_{random_string}'.format(prefix=prefix, random_string=get_random_string())
else:
username = get_random_string()
if admin:
User.objects.create_superuser(username=username, email='', password='123')
else:
User.objects.create_user(username=username, email='', password='123')
Использование:
python manage.py create_users 2 --admin
Или
python manage.py create_users 2 -a
Произвольный список аргументов
Давайте создадим новую команду delete_users
. В ней мы передадим список идентификаторов пользователей, которых команда должна удалить из базы данных.
management/commands/delete_users.py
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'Delete users'
def add_arguments(self, parser):
parser.add_argument('user_id', nargs='+', type=int, help='User ID')
def handle(self, *args, **kwargs):
users_ids = kwargs['user_id']
for user_id in users_ids:
try:
user = User.objects.get(pk=user_id)
user.delete()
self.stdout.write(u'Пользователь"%s (%s)" удален успешно!' % (user.username, user_id))
except User.DoesNotExist:
self.stdout.write(u'Пользователь с id "%s" не существует.' % user_id)
Использование:
python manage.py delete_users 1
Результат:
Пользователь "SMl5ISqAsIS8 (1)" удален успешно!
Также мы можем передать список id
, разделенных пробелом, таким образом команда удалит пользователей за один вызов:
python manage.py delete_users 1 2 3 4
Результат:
Пользователь с id "1" не существует. Пользователь "9teHR4Y7Bz4q (2)" удален успешно! Пользователь "ABdSgmBtfO2t (3)" удален успешно! Пользователь "BsDxOO8Uxgvo (4)" удален успешно!
Стилизация
Можно улучшить предыдущий пример, добавив цвета для вывода сообщений:
management/commands/delete_users.py
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'Delete users'
def add_arguments(self, parser):
parser.add_argument('user_id', nargs='+', type=int, help='User ID')
def handle(self, *args, **kwargs):
users_ids = kwargs['user_id']
for user_id in users_ids:
try:
user = User.objects.get(pk=user_id)
user.delete()
self.stdout.write(self.style.SUCCESS(u'Пользователь "%s (%s)" удален успешно!' % (user.username, user_id)))
except User.DoesNotExist:
self.stdout.write(self.style.WARNING(u'Пользователь с id "%s" не существует.' % user_id))
Использование:
python manage.py delete_users 3 4 5 6
Результат:
Ниже предоставлен список всех доступных стилей в виде пользовательской команды:
from django.core.management.base import BaseCommand
class Command(BaseCommand):
help = 'Show all available styles'
def handle(self, *args, **kwargs):
self.stdout.write(self.style.ERROR('error - A major error.'))
self.stdout.write(self.style.NOTICE('notice - A minor error.'))
self.stdout.write(self.style.SUCCESS('success - A success.'))
self.stdout.write(self.style.WARNING('warning - A warning.'))
self.stdout.write(self.style.SQL_FIELD('sql_field - The name of a model field in SQL.'))
self.stdout.write(self.style.SQL_COLTYPE('sql_coltype - The type of a model field in SQL.'))
self.stdout.write(self.style.SQL_KEYWORD('sql_keyword - An SQL keyword.'))
self.stdout.write(self.style.SQL_TABLE('sql_table - The name of a model in SQL.'))
self.stdout.write(self.style.HTTP_INFO('http_info - A 1XX HTTP Informational server response.'))
self.stdout.write(self.style.HTTP_SUCCESS('http_success - A 2XX HTTP Success server response.'))
self.stdout.write(self.style.HTTP_NOT_MODIFIED('http_not_modified - A 304 HTTP Not Modified server response.'))
self.stdout.write(self.style.HTTP_REDIRECT('http_redirect - A 3XX HTTP Redirect server response other than 304.'))
self.stdout.write(self.style.HTTP_NOT_FOUND('http_not_found - A 404 HTTP Not Found server response.'))
self.stdout.write(self.style.HTTP_BAD_REQUEST('http_bad_request - A 4XX HTTP Bad Request server response other than 404.'))
self.stdout.write(self.style.HTTP_SERVER_ERROR('http_server_error - A 5XX HTTP Server Error response.'))
self.stdout.write(self.style.MIGRATE_HEADING('migrate_heading - A heading in a migrations management command.'))
self.stdout.write(self.style.MIGRATE_LABEL('migrate_label - A migration name.'))
Задания для утилиты cron
Если у вас есть задачи, которые должны периодически выполняться, можно использовать свою команду и добавить ее в crontab:
# m h dom mon dow command 0 4 * * * /home/mysite/venv/bin/python /home/mysite/mysite/manage.py my_custom_command
Этот пример будет выполнять каждый день в 4 часа команду my_custom_command
.