Кастомные команды manage.py
Django позволяет создавать собственные команды для автоматизации задач. Это мощный инструмент для разработчиков, который помогает автоматизировать рутинные операции, создавать фикстуры данных, выполнять миграции и многое другое.
Структура команды
Базовая структура кастомной команды Django выглядит следующим образом:
Создание команды с аргументами
Для создания более функциональных команд используй метод add_arguments
:
1from django.core.management.base import BaseCommand
2from django.contrib.auth.models import User
3
4class Command(BaseCommand):
5 help = 'Создает суперпользователя с указанными данными'
6
7 def add_arguments(self, parser):
8 parser.add_argument('username', type=str, help='Имя пользователя')
9 parser.add_argument('email', type=str, help='Email пользователя')
10 parser.add_argument('--password', type=str, default='admin123', help='Пароль пользователя')
11
12 def handle(self, *args, **options):
13 username = options['username']
14 email = options['email']
15 password = options['password']
16
17 if User.objects.filter(username=username).exists():
18 self.stdout.write(
19 self.style.WARNING(f'Пользователь {username} уже существует')
20 )
21 return
22
23 user = User.objects.create_superuser(username, email, password)
24 self.stdout.write(
25 self.style.SUCCESS(f'Суперпользователь {username} успешно создан')
26 )
Команда для работы с базой данных
Пример команды для очистки устаревших данных:
1from django.core.management.base import BaseCommand
2from django.utils import timezone
3from datetime import timedelta
4from your_app.models import LogEntry
5
6class Command(BaseCommand):
7 help = 'Очищает логи старше указанного количества дней'
8
9 def add_arguments(self, parser):
10 parser.add_argument(
11 '--days',
12 type=int,
13 default=30,
14 help='Количество дней для хранения логов'
15 )
16
17 def handle(self, *args, **options):
18 days = options['days']
19 cutoff_date = timezone.now() - timedelta(days=days)
20
21 deleted_count, _ = LogEntry.objects.filter(
22 created_at__lt=cutoff_date
23 ).delete()
24
25 self.stdout.write(
26 self.style.SUCCESS(f'Удалено {deleted_count} устаревших записей')
27 )
Команда для создания фикстур
Автоматическое создание тестовых данных:
1from django.core.management.base import BaseCommand
2from django.contrib.auth.models import User
3from faker import Faker
4
5class Command(BaseCommand):
6 help = 'Создает тестовых пользователей для разработки'
7
8 def add_arguments(self, parser):
9 parser.add_argument(
10 '--count',
11 type=int,
12 default=10,
13 help='Количество пользователей для создания'
14 )
15
16 def handle(self, *args, **options):
17 fake = Faker('ru_RU')
18 count = options['count']
19
20 for i in range(count):
21 username = fake.user_name()
22 email = fake.email()
23
24 if not User.objects.filter(username=username).exists():
25 User.objects.create_user(username, email, 'password123')
26 self.stdout.write(f'Создан пользователь: {username}')
27 else:
28 self.stdout.write(f'Пользователь {username} уже существует')
Команда с выбором действий
Использование флагов для различных операций:
1from django.core.management.base import BaseCommand
2from django.core.cache import cache
3
4class Command(BaseCommand):
5 help = 'Управление кешем приложения'
6
7 def add_arguments(self, parser):
8 parser.add_argument(
9 '--clear',
10 action='store_true',
11 help='Очистить весь кеш'
12 )
13 parser.add_argument(
14 '--status',
15 action='store_true',
16 help='Показать статус кеша'
17 )
18
19 def handle(self, *args, **options):
20 if options['clear']:
21 cache.clear()
22 self.stdout.write(
23 self.style.SUCCESS('Кеш успешно очищен')
24 )
25 elif options['status']:
26 # Простая проверка кеша
27 cache.set('test_key', 'test_value', 60)
28 if cache.get('test_key') == 'test_value':
29 self.stdout.write(
30 self.style.SUCCESS('Кеш работает корректно')
31 )
32 else:
33 self.stdout.write(
34 self.style.ERROR('Проблемы с кешем')
35 )
36 else:
37 self.stdout.write('Используй --clear для очистки или --status для проверки')
Команда для планировщика задач
Создание команды для cron-задач:
1from django.core.management.base import BaseCommand
2from django.core.mail import send_mail
3from django.conf import settings
4from your_app.models import User, Subscription
5
6class Command(BaseCommand):
7 help = 'Отправляет уведомления о истекающих подписках'
8
9 def handle(self, *args, **options):
10 # Находим подписки, которые истекают через 3 дня
11 expiring_subscriptions = Subscription.objects.filter(
12 expires_at__lte=timezone.now() + timedelta(days=3),
13 is_active=True
14 ).select_related('user')
15
16 for subscription in expiring_subscriptions:
17 user = subscription.user
18 send_mail(
19 subject='Подписка истекает',
20 message=f'Уважаемый {user.username}, ваша подписка истекает {subscription.expires_at}',
21 from_email=settings.DEFAULT_FROM_EMAIL,
22 recipient_list=[user.email],
23 )
24
25 self.stdout.write(f'Уведомление отправлено пользователю {user.username}')
26
27 self.stdout.write(
28 self.style.SUCCESS(f'Обработано {expiring_subscriptions.count()} подписок')
29 )
Лучшие практики
- Используй описательные имена команд - они должны быть понятными и отражать функциональность
- Добавляй help текст - это поможет другим разработчикам понять назначение команды
- Обрабатывай ошибки - используй try-except блоки для корректной обработки исключений
- Используй стили вывода -
self.style.SUCCESS
,self.style.WARNING
,self.style.ERROR
- Добавляй прогресс-бары - для длительных операций используй
tqdm
или встроенные средства Django - Тестируй команды - создавай unit-тесты для проверки корректности работы
Тестирование команд
Пример теста для кастомной команды:
1from django.test import TestCase
2from django.core.management import call_command
3from django.core.management.base import CommandError
4from django.contrib.auth.models import User
5from io import StringIO
6
7class CreateUserCommandTest(TestCase):
8 def test_create_user_command(self):
9 out = StringIO()
10 call_command('create_user', 'testuser', 'test@example.com', stdout=out)
11
12 self.assertIn('пользователь testuser успешно создан', out.getvalue())
13 self.assertTrue(User.objects.filter(username='testuser').exists())
14
15 def test_create_existing_user(self):
16 User.objects.create_user('existinguser', 'existing@example.com')
17 out = StringIO()
18 call_command('create_user', 'existinguser', 'new@example.com', stdout=out)
19
20 self.assertIn('уже существует', out.getvalue())
FAQ
Q: Как добавить аргументы в команду?
A: Используй метод add_arguments
для определения параметров командной строки. Поддерживаются различные типы: строки, числа, булевы значения, выбор из списка.
Q: Как запустить команду из кода?
A: Используй call_command('имя_команды', *args, **options)
для программного вызова команд.
Q: Можно ли использовать команды в production?
A: Да, но будь осторожен с командами, которые изменяют данные. Всегда делай бэкапы и тестируй на staging-окружении.
Q: Как добавить интерактивность в команду?
A: Используй input()
для получения данных от пользователя или click
библиотеку для более продвинутого CLI.
Q: Где размещать кастомные команды?
A: В папке management/commands/
внутри Django-приложения. Django автоматически обнаружит все команды.