Кастомные команды manage.py

Django позволяет создавать собственные команды для автоматизации задач. Это мощный инструмент для разработчиков, который помогает автоматизировать рутинные операции, создавать фикстуры данных, выполнять миграции и многое другое.

Структура команды

Базовая структура кастомной команды Django выглядит следующим образом:

1from django.core.management.base import BaseCommand
2
3class Command(BaseCommand):
4    help = 'Описание команды'
5
6    def handle(self, *args, **options):
7        self.stdout.write('Команда выполнена!')

Создание команды с аргументами

Для создания более функциональных команд используй метод 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 автоматически обнаружит все команды.