Управление Django-проектом через manage.py: от основ до продвинутых техник

Раздел: Веб-разработка -> Django

Что такое manage.py и зачем он нужен

Файл manage.py создается автоматически при старте Django-проекта. Он является точкой входа для большинства операций: запуск сервера, миграции, создание суперпользователя, тестирование и многих других. В основе лежит модуль django.core.management, который загружает настройки проекта и вызывает указанную команду.

Основное эффективное решение: использование встроенных команд

Самый распространённый сценарий – вызов стандартных команд через python manage.py [команда]. Например:

python manage.py runserver 0.0.0.0:8000
python manage.py migrate
python manage.py createsuperuser
python manage.py test myapp

фреймворк python django (фреймворк django в python)

Для просмотра полного списка команд выполните python manage.py help. Каждая команда может принимать дополнительные аргументы, которые перечисляются после --help (например, python manage.py runserver --help).

Типичная ошибка: ImportError: No module named 'myapp' – приложение не добавлено в INSTALLED_APPS. Решение: проверьте файл settings.py и добавьте имя приложения в список.

Проблема: команда не найдена, хотя она стандартная. Причина – устаревшая версия Django или повреждённый файл manage.py. Восстановите manage.py командой django-admin startproject в новой папке и перенесите настройки.

Как создать собственную управляющую команду?

Для расширения функционала проекта можно писать кастомные команды. Они размещаются в файле management/commands/имя_команды.py внутри приложения. Команды наследуются от BaseCommand и реализуют метод handle(self, *args, **options).

# myapp/management/commands/hello.py
from django.core.management.base import BaseCommand

class Command(BaseCommand):
    help = 'Выводит приветствие'

    def add_arguments(self, parser):
        parser.add_argument('name', type=str, help='Имя пользователя')
        parser.add_argument('--uppercase', action='store_true', help='Вывести заглавными буквами')

    def handle(self, *args, **options):
        name = options['name']
        if options['uppercase']:
            name = name.upper()
        self.stdout.write(f'Привет, {name}!')

Python manage py (manage.py в django)

# Вызов:
python manage.py hello Иван --uppercase
# Результат:
# Привет, ИВАН!

Python run manage py (запуск manage.py в django)

Ошибка: ModuleNotFoundError: No module named 'myapp' – приложение не зарегистрировано. Добавьте его в INSTALLED_APPS и выполните python manage.py makemigrations (если есть модели).

Проблема: команда не появляется в списке. Проверьте структуру папок: должен быть файл myapp/management/__init__.py и myapp/management/commands/__init__.py (оба пустые).

Как использовать manage.py в скриптах и автоматизации?

Иногда требуется вызвать команду из другого Python-кода, например, в фоновом задании или после определённого события. Django предоставляет функцию call_command из django.core.management.

# В любом месте проекта (после настройки DJANGO_SETTINGS_MODULE)
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

import django
django.setup()

from django.core.management import call_command

# Запуск миграций
call_command('migrate', '--verbosity', '3')

# Запуск своей команды с аргументами
call_command('hello', 'Мария', uppercase=True)

Python manage py migrate (команда migrate в django)

Если нужно выполнить команду в оболочке ОС (например, в cron), используйте subprocess:

import subprocess
subprocess.run(['python', 'manage.py', 'hello', 'Алексей'])

Проблема: django.core.exceptions.AppRegistryNotReady при вызове call_command до django.setup(). Решение: всегда сначала вызывать django.setup() после установки переменной окружения.

Ошибка: команда не принимает аргументы в call_command – передавайте их как **kwargs (uppercase=True).

Как настроить manage.py для разных окружений (разработка, тестирование, продакшн)?

Обычно manage.py подключает настройки из переменной окружения DJANGO_SETTINGS_MODULE. Можно изменить manage.py, чтобы он поддерживал аргумент --settings или автоматически выбирал файл на основе хоста.

# Кастомный manage.py (пример)
import os
import sys

def main():
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings.dev')
    try:
        from django.core.management import execute_from_command_line
    except ImportError as exc:
        raise ImportError("Django не установлен") from exc
    execute_from_command_line(sys.argv)

if __name__ == '__main__':
    main()

Теперь можно создать несколько файлов настроек: settings/dev.py, settings/prod.py и переключаться между ними через переменную окружения:

export DJANGO_SETTINGS_MODULE=myproject.settings.prod
python manage.py runserver

Проблема: при использовании множества settings-файлов возникает путаница с бэкендами базы данных. Убедитесь, что каждый файл имеет корректные импорты (например, from .base import *).

Ошибка: manage.py не видит settings. Проверьте, что файл settings/__init__.py существует (может быть пустым).

Расширенные примеры работы с manage.py

Пример 1. Кастомная команда с обработкой ошибок и логированием

Создадим команду, которая импортирует данные из CSV в модель Product. Допустим, есть модель:

Пример
# myapp/models.py
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    created_at = models.DateTimeField(auto_now_add=True)

Команда:

Пример
# myapp/management/commands/import_products.py
import csv
import logging
from django.core.management.base import BaseCommand, CommandError
from myapp.models import Product

logger = logging.getLogger(__name__)

class Command(BaseCommand):
    help = 'Импортирует товары из CSV файла'

    def add_arguments(self, parser):
        parser.add_argument('csv_file', type=str, help='Путь к CSV файлу')
        parser.add_argument('--delimiter', type=str, default=',', help='Разделитель полей (по умолчанию запятая)')
        parser.add_argument('--dry-run', action='store_true', help='Тестовый прогон без сохранения')

    def handle(self, *args, **options):
        file_path = options['csv_file']
        delimiter = options['delimiter']
        dry_run = options['dry_run']

        try:
            with open(file_path, newline='', encoding='utf-8') as f:
                reader = csv.DictReader(f, delimiter=delimiter)
                if 'name' not in reader.fieldnames or 'price' not in reader.fieldnames:
                    raise CommandError('Ожидаются столбцы "name" и "price"')

                count = 0
                for row in reader:
                    name = row['name'].strip()
                    price = row['price'].strip()
                    if not name or not price:
                        logger.warning(f'Пропущена строка {row}')
                        continue
                    try:
                        price = float(price)
                    except ValueError:
                        raise CommandError(f'Неверный формат цены: {price}')

                    if not dry_run:
                        Product.objects.get_or_create(name=name, defaults={'price': price})
                    count += 1

                self.stdout.write(self.style.SUCCESS(f'Обработано {count} записей. Dry-run: {dry_run}'))
        except FileNotFoundError:
            raise CommandError(f'Файл {file_path} не найден')

Пример вызова:

Пример
python manage.py import_products products.csv --delimiter ';' --dry-run
# Вывод (если ошибок нет):
# Обработано 10 записей. Dry-run: True
При реальном импорте (без --dry-run) записи будут сохранены в базу данных.

Пример 2. Использование call_command с транзакцией и отловом исключений

Часто требуется вызвать несколько команд в одной транзакции. Можно использовать декоратор transaction.atomic:

Пример
from django.db import transaction
from django.core.management import call_command

@transaction.atomic
def deploy_updates():
    """Применить изменения базы данных и загрузить начальные данные"""
    call_command('migrate', '--fake-initial')
    call_command('loaddata', 'initial_data.json')
    call_command('collectstatic', '--noinput')

Если одна из команд вызовет исключение, все изменения откатятся. Полезно для скриптов развёртывания.

Результат: все команды выполнятся последовательно, при ошибке база вернётся к исходному состоянию.

Пример 3. Вывод информации о проекте через manage.py

Стандартная команда check проверяет проект на наличие проблем, но можно написать свою для вывода версий приложений или настроек.

Пример
# myapp/management/commands/project_info.py
from django.conf import settings
from django.core.management.base import BaseCommand

class Command(BaseCommand):
    help = 'Показывает информацию о проекте'

    def handle(self, *args, **options):
        self.stdout.write(f'DEBUG: {settings.DEBUG}')
        self.stdout.write(f'База данных: {settings.DATABASES["default"]["ENGINE"]}')
        self.stdout.write(f'Установленные приложения: {len(settings.INSTALLED_APPS)}')
        self.stdout.write(f'BROKER_URL: {getattr(settings, "CELERY_BROKER_URL", "не настроен")}')
        # Вывод списка URL-шаблонов (если доступны)
        try:
            from django.urls import get_resolver
            from django.urls.resolvers import URLPattern
            resolver = get_resolver()
            self.stdout.write('URL-шаблоны:')
            for pattern in resolver.url_patterns:
                if isinstance(pattern, URLPattern):
                    self.stdout.write(f'  {pattern.pattern}')
        except Exception as e:
            self.stdout.write(self.style.WARNING(f'Не удалось прочитать URL: {e}'))

Пример вызова и вывода:

Пример
python manage.py project_info
DEBUG: True
База данных: django.db.backends.sqlite3
Установленные приложения: 12
BROKER_URL: не настроен
URL-шаблоны:
  admin/
  api/

Пример 4. Запуск нескольких команд в одном процессе с передачей контекста

Иногда нужно выполнить команды, которые зависят друг от друга. Например, инициализация демо-данных после миграции.

Пример
from django.core.management import call_command, execute_from_command_line

# Прямой вызов в скрипте
def setup_demo():
    call_command('migrate')
    call_command('loaddata', 'demo_data')
    call_command('rebuild_index', verbosity=0)

# Вызов из manage.py с передачей всей строки через execute_from_command_line
# (этот метод принимает список аргументов, например, из sys.argv)
execute_from_command_line(['manage.py', 'migrate'])
execute_from_command_line(['manage.py', 'loaddata', 'demo_data'])

Второй способ полезен, когда нужно динамически формировать аргументы. Однако каждый вызов перезагружает проекты, что неэффективно. Лучше использовать call_command.

Пример 5. Кастомная команда с прогресс-баром

Для длительных операций можно отображать индикатор выполнения с помощью tqdm или встроенного self.stdout.write с перезаписью строки.

Пример
# myapp/management/commands/bulk_update_prices.py
from django.core.management.base import BaseCommand
from myapp.models import Product
import time

class Command(BaseCommand):
    help = 'Обновляет цены всех товаров с заданным коэффициентом'

    def add_arguments(self, parser):
        parser.add_argument('factor', type=float, help='Коэффициент увеличения цены')

    def handle(self, *args, **options):
        factor = options['factor']
        qs = Product.objects.all()
        total = qs.count()
        self.stdout.write(f'Обновление {total} товаров...')
        for idx, product in enumerate(qs.iterator(), 1):
            product.price = float(product.price) * factor
            product.save(update_fields=['price'])
            if idx % 10 == 0:
                self.stdout.write(f'\rОбработано {idx}/{total}', ending='')
                self.stdout.flush()
        self.stdout.write(f'\nОбновление завершено')
Вывод в консоли:
Обновление 100 товаров...
Обработано 30/100
Обработано 40/100
...
Обработано 100/100
Обновление завершено

manage.py в Django - comments

En
Python manage py (python)