Блок 1 — Python Basic (1–6)
- Лекция 1. Введение. Типизации. Переменные. Строки и числа. Булева алгебра. Ветвление
- Лекция 2. Обработка исключений. Списки, строки детальнее, срезы, циклы.
- Лекция 3. None. Range, list comprehension, sum, max, min, len, sorted, all, any. Работа с файлами
- Лекция 4. Хеш-таблицы. Set, frozenset. Dict. Tuple. Немного об импортах. Namedtuple, OrderedDict
- Лекция 5. Функции, типизация, lambda. Map, zip, filter.
- Лекция 6. Алгоритмы и структуры данных
Блок 2 — Git (7–8)
Блок 3 — Python Advanced (9–14)
- Лекция 9. Введение в ООП. Основные парадигмы ООП. Классы и объекты. Множественное наследование.
- Лекция 10. Magic methods. Итераторы и генераторы.
- Лекция 11. Imports. Standard library. PEP 8
- Лекция 12. Декораторы. Декораторы с параметрами. Декораторы классов (staticmethod, classmethod, property)
- Лекция 13. Тестирование
- Лекция 14. Проектирование. Паттерны. SOLID.
Блок 4 — SQL (15–17)
Блок 5 — Django (19–26)
- Лекция 19. Знакомство с Django
- Лекция 20. Templates. Static
- Лекция 21. Модели. Связи. Meta. Abstract, proxy
- Лекция 22. Django ORM
- Лекция 23. Forms, ModelForms. User, Authentication
- Лекция 24. ClassBaseView
- Лекция 25. NoSQL. Куки, сессии, кеш
- ▶ Лекция 26. Логирование. Middleware. Signals. Background Tasks. Messages. Manage commands
Блок 6 — Django Rest Framework (27–30)
Блок 7 — Python async (31–33)
Блок 8 — Deployment (34–35)
В Django помимо представлений важно уметь наблюдать и управлять жизненным циклом запроса. В этом уроке мы начнём с логирования (наблюдаемость), затем перейдём к middleware (сквозные обработки request/response), signals (разделение побочных эффектов), background tasks (фоновые задачи), messages (flash‑сообщения для UI) и кратко затронем manage‑команды.
Куда что применять кратко:
- Логирование — наблюдаемость, отладка, аудит. Используем logging + конфигурацию LOGGING.
- Middleware — быстрые и stateless сквозные операции; тяжёлую логику не размещаем.
- Signals — реакция на события модели/системы; тяжёлые задачи выносим в очереди, используем dispatch_uid.
- Background Tasks (Django 6.0+) — встроенный фреймворк для фоновых задач; сигнал ставит задачу, воркер выполняет.
- Messages — дружелюбная коммуникация с пользователем между перенаправлениями.
- Manage‑команды и data migrations — операции обслуживания и изменения данных (упомянем современный
RunPython.noop).
Логирование — это процесс записи информации о работе приложения. В Django используется встроенный модуль Python logging, который настраивается через словарь LOGGING в settings.py.
- Отладка проблем в продакшене
- Мониторинг производительности
- Аудит действий пользователей
- Анализ ошибок и исключений
Минимальная конфигурация в settings.py:
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
"style": "{",
},
"simple": {
"format": "{levelname} {message}",
"style": "{",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "simple",
},
"file": {
"class": "logging.FileHandler",
"filename": "django.log",
"formatter": "verbose",
},
},
"root": {
"handlers": ["console"],
"level": "INFO",
},
"loggers": {
"django": {
"handlers": ["console", "file"],
"level": "INFO",
"propagate": False,
},
"myapp": { # замените на имя вашего приложения
"handlers": ["console", "file"],
"level": "DEBUG",
"propagate": False,
},
},
}import logging
logger = logging.getLogger(__name__)
def my_view(request):
logger.debug("Начало обработки запроса")
logger.info("Пользователь %s выполнил действие", request.user.username)
try:
# ваш код
result = some_operation()
logger.info("Операция выполнена успешно")
return result
except Exception as e:
logger.error("Ошибка при выполнении операции: %s", e, exc_info=True)
raise| Уровень | Числовое значение | Когда использовать |
|---|---|---|
DEBUG |
10 | Детальная информация для отладки |
INFO |
20 | Общая информация о работе |
WARNING |
30 | Предупреждения о потенциальных проблемах |
ERROR |
40 | Ошибки, которые не останавливают работу |
CRITICAL |
50 | Критические ошибки, приложение может упасть |
Полезно для отладки N+1 проблем и оптимизации:
# settings.py (только для разработки!)
LOGGING = {
# ... остальные настройки ...
"loggers": {
"django.db.backends": {
"handlers": ["console"],
"level": "DEBUG", # Покажет все SQL-запросы
"propagate": False,
},
},
}Для production рекомендуется JSON-формат — его легко парсить системами мониторинга:
# settings.py
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
"format": "%(asctime)s %(levelname)s %(name)s %(message)s",
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "json",
},
},
"root": {
"handlers": ["console"],
"level": "INFO",
},
}Установка: pip install python-json-logger
Вывод будет таким:
{"asctime": "2025-01-15 10:30:00", "levelname": "INFO", "name": "myapp.views", "message": "User logged in", "user_id": 123}Sentry — популярный сервис для отслеживания ошибок:
# settings.py
import sentry_sdk
from sentry_sdk.integrations.django import DjangoIntegration
sentry_sdk.init(
dsn="https://your-dsn@sentry.io/project",
integrations=[DjangoIntegration()],
traces_sample_rate=0.1, # 10% запросов для performance monitoring
send_default_pii=True,
)Для больших проектов логи отправляют в Elasticsearch:
Приложение → Filebeat → Logstash → Elasticsearch → Kibana
↓
JSON-логи в файл или stdout
💡 Совет: В Docker/Kubernetes логируйте в stdout — оркестратор сам соберёт логи.
Добавляйте контекст к логам для удобства отладки:
import logging
logger = logging.getLogger(__name__)
def process_order(request, order_id):
# Создаём адаптер с дополнительным контекстом
extra = {
"user_id": request.user.id,
"order_id": order_id,
"ip": request.META.get("REMOTE_ADDR"),
}
logger.info("Processing order", extra=extra)
try:
order = Order.objects.get(pk=order_id)
# ... обработка
logger.info("Order processed successfully", extra=extra)
except Order.DoesNotExist:
logger.warning("Order not found", extra=extra)
except Exception as e:
logger.exception("Failed to process order", extra=extra)
raiseМы с вами рассмотрели основные этапы того, какие этапы должен пройти request на всём пути нашей request-response системы, но на самом деле каждый request проходит кучу дополнительных обработок, таких как middleware, причём каждый request делает это дважды, при «входе» и при «выходе».
Middleware — это фреймворк обработки запросов и ответов. Каждый HTTP-запрос проходит через цепочку middleware «вперёд» к view и «обратно» — к пользователю. Это похоже на слоёный пирог: каждый слой может что-то сделать с запросом или ответом.
Request ↓
┌─────────────────────────────────────────────────────────┐
│ SecurityMiddleware │
│ SessionMiddleware │
│ CommonMiddleware │
│ CsrfViewMiddleware │
│ AuthenticationMiddleware │
│ YOUR CUSTOM MIDDLEWARE │
│ MessageMiddleware │
│ Clickjacking Protection (XFrameOptionsMiddleware) │
└─────────────────────────────────────────────────────────┘
View →
← Response (тот же путь обратно)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'main.middleware.CheckUserStatus', # ваш кастомный
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]import logging
logger = logging.getLogger(__name__)
def simple_middleware(get_response):
def middleware(request):
logger.debug("Before view")
response = get_response(request)
logger.debug("After view")
return response
return middlewareimport logging
from django.http import HttpResponse
logger = logging.getLogger(__name__)
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
logger.debug("Before view")
# Пример короткого замыкания запроса:
# if some_condition(request):
# return HttpResponse("Blocked", status=403)
response = self.get_response(request)
logger.debug("After view")
return responseДобавьте middleware в settings.py.
import time
import logging
logger = logging.getLogger(__name__)
class RequestTimingMiddleware:
"""Логирует время выполнения каждого запроса."""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
start_time = time.perf_counter()
response = self.get_response(request)
duration = time.perf_counter() - start_time
logger.info(
"Request %s %s completed in %.3fs",
request.method,
request.path,
duration,
)
# Можно добавить заголовок в ответ
response["X-Request-Duration"] = f"{duration:.3f}s"
return responseimport uuid
class RequestIDMiddleware:
"""Добавляет уникальный ID к каждому запросу для трассировки."""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Берём ID из заголовка или генерируем новый
request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())
request.request_id = request_id
response = self.get_response(request)
response["X-Request-ID"] = request_id
return responsefrom django.http import HttpResponseForbidden
from django.conf import settings
class IPBlockMiddleware:
"""Блокирует запросы с определённых IP-адресов."""
def __init__(self, get_response):
self.get_response = get_response
self.blocked_ips = getattr(settings, "BLOCKED_IPS", [])
def __call__(self, request):
ip = self.get_client_ip(request)
if ip in self.blocked_ips:
return HttpResponseForbidden("Access denied")
return self.get_response(request)
def get_client_ip(self, request):
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
if x_forwarded_for:
return x_forwarded_for.split(",")[0].strip()
return request.META.get("REMOTE_ADDR")Помимо __call__, middleware может определять специальные методы:
class AdvancedMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_view(self, request, view_func, view_args, view_kwargs):
"""
Вызывается ПЕРЕД вызовом view, но ПОСЛЕ URL routing.
Можно проверить, какой view будет вызван.
Return None — продолжить выполнение
Return HttpResponse — прервать и вернуть ответ
"""
# Пример: логирование вызываемого view
logger.debug(f"Calling view: {view_func.__name__}")
return None
def process_exception(self, request, exception):
"""
Вызывается, если view выбросил исключение.
Return None — исключение обрабатывается стандартно
Return HttpResponse — вернуть этот ответ вместо ошибки
"""
logger.exception(f"Exception in view: {exception}")
# Можно вернуть кастомную страницу ошибки
return None
def process_template_response(self, request, response):
"""
Вызывается, если response имеет метод render() (TemplateResponse).
Позволяет модифицировать контекст шаблона.
"""
if hasattr(response, "context_data"):
response.context_data["middleware_data"] = "injected"
return responseRequest ↓
1. Middleware1.__call__ (before get_response)
2. Middleware2.__call__ (before get_response)
3. process_view (все middleware, сверху вниз)
4. View выполняется
5. process_exception (если была ошибка, снизу вверх)
6. process_template_response (если TemplateResponse, снизу вверх)
7. Middleware2.__call__ (after get_response)
8. Middleware1.__call__ (after get_response)
Response ↑
Порядок имеет значение! Если вы добавили middleware, работающий с request.user, он должен быть после
AuthenticationMiddleware.
Для совместимости с устаревшими API:
from django.utils.deprecation import MiddlewareMixin
class MyMiddleware(MiddlewareMixin):
def process_request(self, request):
...
def process_response(self, request, response):
...
⚠️ MiddlewareMixin— это legacy-подход. Для новых проектов используйте классовый middleware с__call__.
📚 Signals
Сигналы — способ уведомить части вашего приложения о том, что «что-то произошло», — например, сохранение объекта. Это помогает разделить ответственность и не писать логику в моделях или view напрямую.
| Сигнал | Когда вызывается |
|---|---|
pre_save / post_save |
До/после сохранения объекта |
pre_delete / post_delete |
До/после удаления объекта |
m2m_changed |
Изменения в ManyToMany |
request_started / request_finished |
Начало/конец запроса |
pre_migrate / post_migrate |
До/после миграций |
user_logged_in / user_logged_out |
Вход/выход пользователя |
Когда что использовать?
| Критерий | Сигналы | Переопределение save() |
|---|---|---|
| Логика связана с моделью напрямую | ❌ | ✅ Лучший выбор |
| Логика в другом приложении | ✅ Разделение ответственности | ❌ Создаёт зависимость |
| Нужно реагировать на bulk-операции | ❌ Сигналы не вызываются | ❌ save() тоже не вызывается |
| Отладка и читаемость | ❌ Скрытая логика | ✅ Явная логика |
| Сторонние приложения | ✅ Единственный способ | ❌ Нельзя изменить |
| Аудит/логирование | ✅ Хороший выбор |
Правило: Если логика — часть бизнес-логики модели, используйте save(). Если это побочный эффект (уведомления, аудит, кеш) — сигналы.
from django.db.models.signals import post_save
from django.dispatch import receiver
from myapp.models import Order
@receiver(post_save, sender=Order)
def after_order_save(sender, instance, created, **kwargs):
if created:
send_email_to_manager(instance)Чтобы избежать двойной регистрации обработчика при повторных импортах/автоперезапусках, используйте
dispatch_uid.
@receiver(post_save, sender=Order, dispatch_uid="order_post_save_once")
def after_order_save(sender, instance, created, **kwargs):
...- Тяжёлые операции в сигнале (отправка email, внешние API) лучше выносить в очередь задач (Celery/RQ) — сигнал только ставит задачу.
- В современных версиях Django для кастомных сигналов не требуется
providing_args— параметр удалён.
Сигнал m2m_changed вызывается при изменении ManyToMany связей:
from django.db.models.signals import m2m_changed
from django.dispatch import receiver
from myapp.models import Article
@receiver(m2m_changed, sender=Article.tags.through)
def tags_changed(sender, instance, action, pk_set, **kwargs):
"""
Вызывается при изменении тегов статьи.
action может быть:
- "pre_add" / "post_add" — перед/после добавления
- "pre_remove" / "post_remove" — перед/после удаления
- "pre_clear" / "post_clear" — перед/после очистки всех связей
"""
if action == "post_add":
# pk_set — множество ID добавленных тегов
print(f"Added tags {pk_set} to article {instance.title}")
# Пример: инвалидация кеша
cache.delete(f"article_{instance.id}_tags")
elif action == "post_remove":
print(f"Removed tags {pk_set} from article {instance.title}")
elif action == "post_clear":
print(f"Cleared all tags from article {instance.title}")
⚠️ Важно:sender— это промежуточная таблица (Article.tags.through), а не сама модель!
from django.dispatch import Signal
pizza_done = Signal()
# Отправка сигнала
pizza_done.send(sender=None, toppings=["pepperoni"], size="large")
# Подписка на сигнал
@receiver(pizza_done)
def on_pizza_done(sender, toppings, size, **kwargs):
print(f"Pizza {size} with {toppings} is ready!")- Скрытая логика: сигналы вызываются «в фоне», усложняя отладку.
- Неявные зависимости: сложно понять, кто слушает сигнал.
- Ошибки в сигналах → падает всё (если не обработать исключение).
- Не работают с bulk-операциями:
QuerySet.update(),bulk_create()не вызывают сигналы.
# ❌ Сигнал post_save НЕ вызовется!
Order.objects.filter(status="pending").update(status="completed")
# ✅ Сигнал вызовется для каждого объекта
for order in Order.objects.filter(status="pending"):
order.status = "completed"
order.save()Используйте осознанно и выборочно, особенно в больших проектах.
Обычно сигналы регистрируются в отдельном модуле signals.py внутри вашего приложения, а затем подключаются в
apps.py.
myapp/
├── apps.py
├── models.py
├── signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Order
@receiver(post_save, sender=Order)
def notify_manager(sender, instance, created, **kwargs):
if created:
print(f"New order created: {instance.id}")from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'myapp'
def ready(self):
import myapp.signals📌 Метод ready() вызывается при старте Django, и именно там следует импортировать модуль с сигналами, чтобы они
были зарегистрированы. Не подключайте сигналы в models.py или __init__.py — это может привести к проблемам с
импортами.
Файл apps.py содержит класс-настройку вашего приложения и используется Django для конфигурации.
from django.apps import AppConfig
class BlogConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'blog'- Регистрировать сигналы
- Выполнять инициализацию сторонних библиотек
- Проверять зависимости
- Настраивать логгеры
- Создавать фоновые задачи или cron-хуки (например, через Celery beat)
📌 Только не загружайте ready() тяжёлыми действиями — он вызывается при каждом запуске процесса Django.
Background Tasks — это встроенный фреймворк Django для выполнения кода вне цикла request-response. Появился в Django 6.0 (декабрь 2025).
Раньше для фоновых задач требовались сторонние решения (Celery, RQ, Dramatiq). Теперь Django предоставляет единый API для определения и постановки задач в очередь, а выполнение остаётся за внешними воркерами.
- Отправка email (не блокируем пользователя)
- Обработка загруженных файлов (ресайз изображений, конвертация)
- Вызовы внешних API (платёжные системы, уведомления)
- Генерация отчётов
- Любые «тяжёлые» операции из сигналов
Задачи определяются декоратором @task:
# myapp/tasks.py
from django.core.mail import send_mail
from django.tasks import task
@task
def send_welcome_email(user_email, username):
"""Отправка приветственного письма новому пользователю."""
return send_mail(
subject="Добро пожаловать!",
message=f"Привет, {username}! Рады видеть вас.",
from_email=None, # использует DEFAULT_FROM_EMAIL
recipient_list=[user_email],
)
@task(priority=10, queue_name="reports")
def generate_monthly_report(user_id, month, year):
"""Генерация отчёта с высоким приоритетом."""
# ... тяжёлая логика
return {"status": "completed", "rows": 1500}from myapp.tasks import send_welcome_email
# В view или сигнале
def register_user(request):
user = User.objects.create(...)
# Ставим задачу в очередь — не блокируем ответ
result = send_welcome_email.enqueue(
user_email=user.email,
username=user.username,
)
# result — это TaskResult с уникальным id
print(f"Task ID: {result.id}")
return redirect("profile")Backends настраиваются в settings.py через TASKS:
# Для разработки — выполняет задачи сразу (синхронно)
TASKS = {
"default": {
"BACKEND": "django.tasks.backends.immediate.ImmediateBackend",
}
}
# Для тестов — сохраняет задачи без выполнения
TASKS = {
"default": {
"BACKEND": "django.tasks.backends.dummy.DummyBackend",
}
}| Backend | Описание | Использование |
|---|---|---|
ImmediateBackend |
Выполняет задачи сразу, синхронно | Разработка |
DummyBackend |
Сохраняет задачи без выполнения | Тестирование |
⚠️ Важно: Для production нужен сторонний backend с реальным воркером (см. Community Ecosystem).
Типичный паттерн — сигнал ставит задачу в очередь:
# myapp/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Order
from .tasks import notify_manager, process_order
@receiver(post_save, sender=Order, dispatch_uid="order_created")
def on_order_created(sender, instance, created, **kwargs):
if created:
# Лёгкая задача — можно в сигнале
notify_manager.enqueue(order_id=instance.id)
# Тяжёлая задача — обязательно в очередь
process_order.enqueue(order_id=instance.id)При использовании транзакций важно ставить задачу после коммита, иначе воркер может не найти объект в БД:
from functools import partial
from django.db import transaction
def create_order(request):
with transaction.atomic():
order = Order.objects.create(...)
# ❌ Неправильно — задача может выполниться до коммита
# process_order.enqueue(order_id=order.id)
# ✅ Правильно — задача ставится после коммита
transaction.on_commit(
partial(process_order.enqueue, order_id=order.id)
)
return redirect("orders")from django.tasks import default_task_backend
# При постановке задачи
result = send_welcome_email.enqueue(user_email="user@example.com", username="John")
task_id = result.id # Сохраняем для последующего использования
# Позже — получаем результат
result = send_welcome_email.get_result(task_id)
# или через backend
result = default_task_backend.get_result(task_id)
# Проверяем статус
print(result.status) # RUNNING, SUCCESSFUL, FAILED
# Обновляем статус
result.refresh()
# Получаем значение (если SUCCESSFUL)
if result.status == "SUCCESSFUL":
print(result.return_value)
# Получаем ошибки (если FAILED)
if result.status == "FAILED":
for error in result.errors:
print(error.exception_class)
print(error.traceback)Задача может получить информацию о себе через контекст:
import logging
from django.tasks import task
logger = logging.getLogger(__name__)
@task(takes_context=True)
def retry_aware_task(context, data):
"""Задача, которая знает о попытках выполнения."""
logger.info(
f"Попытка {context.attempt}, Task ID: {context.task_result.id}"
)
# ... логика-
JSON-сериализация: Аргументы и возвращаемые значения должны быть JSON-сериализуемыми
# ❌ Не работает my_task.enqueue(datetime.now()) # datetime не сериализуется my_task.enqueue(user) # Model instance не сериализуется # ✅ Работает my_task.enqueue(user_id=user.id) # Передаём ID my_task.enqueue(timestamp=datetime.now().isoformat()) # Строка
-
Django не запускает воркеры: Фреймворк только определяет и ставит задачи. Выполнение — ответственность внешней инфраструктуры.
-
Round-trip типов: После JSON-сериализации типы могут измениться
# tuple → list после сериализации my_task.enqueue(data=(1, 2, 3)) # В задаче будет [1, 2, 3]
📌 Django Tasks — это стандартизированный API. Для production используйте совместимые backends из экосистемы, или Celery для сложных сценариев (см. лекцию 31).
Сообщения позволяют показать одноразовое уведомление пользователю после запроса. Пример — «Вы успешно зарегистрировались».
Убедитесь, что:
'django.contrib.messages'вINSTALLED_APPS'django.contrib.messages.middleware.MessageMiddleware'вMIDDLEWAREdjango.contrib.messages.context_processors.messagesвTEMPLATES -> OPTIONS -> context_processors
from django.contrib import messages
def my_view(request):
messages.success(request, "Профиль обновлён!")
return redirect("profile")messages.debug(request, "Отладка")
messages.info(request, "Информация")
messages.success(request, "Успех!")
messages.warning(request, "Предупреждение")
messages.error(request, "Ошибка"){% if messages %}
<ul class="messages">
{% for message in messages %}
<li class="{{ message.tags }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}from django.contrib.messages.views import SuccessMessageMixin
from django.urls import reverse_lazy
class MyView(SuccessMessageMixin, CreateView):
form_class = MyForm
success_message = "Объект успешно создан!"
success_url = reverse_lazy("my-list") # или переопределите get_success_url()По умолчанию используется
FallbackStorage(MESSAGE_STORAGE). При необходимости можно сменить backend в настройкеMESSAGE_STORAGE.
| Сценарий | Пример сообщения |
|---|---|
| Успешная регистрация | messages.success |
| Ошибка заполнения формы | messages.error |
| Предупреждение о балансе | messages.warning |
| Отображение отладочной info | messages.debug |
✅ Middleware — это обработчик запроса и ответа, может быть функцией или классом ✅ Signals позволяют реагировать на действия модели без изменения её кода ✅ Background Tasks (Django 6.0+) — встроенный способ выносить тяжёлые операции из сигналов и view ✅ Messages — это одноразовые уведомления, удобно использовать после redirect'ов ✅ Не забывайте включать нужные middleware и context_processors ✅ Middleware работают в двух направлениях: запрос и ответ ✅ Для production Background Tasks требуют внешний backend с воркерами
Документация по командам: https://docs.djangoproject.com/en/stable/ref/django-admin/
Manage-команды в рамках Django - это возможность запустить скрипт из консоли для выполнения абсолютно различных действий.
Существует три способа запуска manage-команды:
django-admin <command> [options]
python manage.py <command> [options]
python -m django <command> [options]
В случае запуска через django-admin вы можете указать, какой файл настроек использовать при помощи опции --settings.
Если вы запускаете команду через manage.py (самый распространенный способ), файл настроек будет выбран в соответствии
с самим файлом manage.py (Напоминаю структуру, информация о файле настроек для Django проекта находится именно в
файле manage.py).
Мы использовали некоторые команды, но давайте посмотрим подробнее.
| Категория | Команда | Описание | Пример использования |
|---|---|---|---|
| 🏗 Создание проекта и приложений | startproject |
Создает новый Django-проект | django-admin startproject mysite |
startapp |
Создает новое Django-приложение | python manage.py startapp blog |
|
| 🔄 Миграции и работа с БД | makemigrations |
Генерирует файлы миграций на основе моделей | python manage.py makemigrations users |
migrate |
Применяет миграции к базе данных | python manage.py migrate |
|
showmigrations |
Показывает все миграции и их статус | python manage.py showmigrations |
|
sqlmigrate |
Показывает SQL-код конкретной миграции | python manage.py sqlmigrate users 0001 |
|
squashmigrations |
Объединяет несколько миграций в одну | python manage.py squashmigrations users 0001 0005 |
|
flush |
Удаляет все данные из базы (сохраняя структуру) | python manage.py flush |
|
sqlflush |
Показывает SQL-код, который будет выполнен при flush | python manage.py sqlflush |
|
sqlsequencereset |
Сброс последовательностей (id) | python manage.py sqlsequencereset users |
|
inspectdb |
Генерирует модели на основе существующей БД | python manage.py inspectdb > models.py |
|
| 🧪 Тестирование | test |
Запускает unit-тесты | python manage.py test |
| 🧠 Управление пользователями | createsuperuser |
Создание суперпользователя | python manage.py createsuperuser |
changepassword |
Изменение пароля пользователя | python manage.py changepassword admin |
|
| 🌐 Сервер и консоль | runserver |
Запускает локальный сервер разработки | python manage.py runserver 8080 |
shell |
Открывает Django shell | python manage.py shell |
|
dbshell |
Открывает shell для БД (например, psql) |
python manage.py dbshell |
|
| 🗃 Работа с данными (фикстуры) | dumpdata |
Экспорт данных в JSON (фикстуры) | python manage.py dumpdata > data.json |
loaddata |
Импорт данных из JSON-файла | python manage.py loaddata data.json |
|
| 📖 Работа с переводами | makemessages |
Поиск строк для перевода (создает .po) |
python manage.py makemessages -l ru |
compilemessages |
Компиляция .po в .mo для использования |
python manage.py compilemessages |
|
| 📦 Кэш и сессии | createcachetable |
Создает таблицу в БД для хранения кэша | python manage.py createcachetable |
clearsessions |
Удаляет устаревшие сессии из БД | python manage.py clearsessions |
|
| 🔧 Отладка и администрирование | check |
Проверка проекта на ошибки конфигурации | python manage.py check |
diffsettings |
Сравнение текущих и дефолтных настроек | python manage.py diffsettings |
|
sendtestemail |
Отправка тестового письма (если email настроен) | python manage.py sendtestemail user@example.com |
|
| 📜 Пользовательские команды | (имя команды) | Кастомная команда в management/commands/ |
python manage.py close_orders |
Как создавать проект и приложения вы в целом научились, давайте посмотрим на новые команды, или как можно не стандартно использовать старые.
Уже известная вам команда, которая создаёт файлы миграций, и может принимать имя приложения, чтобы создать только для конкретного приложения.
Может принимать важный параметр --empty, при этом флаге создастся пустая миграция, никак не привязанная к моделям.
Выглядеть будет примерно вот так:
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('storages', '0003_auto_20201029_1352'),
]
operations = [
]Тут указано приложение, для которого миграция будет применена, и прошлая миграция, с которой текущая миграция будет связана.
Зачем это вообще надо?
Мы можем в операции добавить любые интересующие нас действия, например, выполнения кода на Python.
Для этого нужно добавить класс RunPython из пакета migrations, который будет принимать два метода, первый будет
выполнен в случае выполнения миграции, второй - в случае отката миграции.
from django.db import migrations
def some_forward_action(apps, schema_editor):
Team = apps.get_model('storages', 'Team')
Team.objects.create(name='B2B')
Team.objects.create(name='CX')
Team.objects.create(name='SFA')
def some_backward_action(apps, schema_editor):
pass
class Migration(migrations.Migration):
dependencies = [
('storages', '0003_auto_20201029_1352'),
]
operations = [
migrations.RunPython(some_forward_action, some_backward_action)
]Такие миграции называются Data Migrations.
Чаще всего для того, чтобы занести какие-либо данные в базу данных на этапе миграции, например, создать заведомо известные объекты, как в моём примере, или для установки вычисляемого значения по умолчанию.
Для обратной миграции чаще всего действия не требуются (хоть и далеко не всегда).
from django.db import migrations
def create_initial_teams(apps, schema_editor):
"""Создаёт начальные команды при миграции."""
Team = apps.get_model('storages', 'Team')
Team.objects.create(name='B2B')
Team.objects.create(name='CX')
Team.objects.create(name='SFA')
class Migration(migrations.Migration):
dependencies = [
('storages', '0003_auto_20201029_1352'),
]
operations = [
# Современный способ — используем RunPython.noop для обратной миграции
migrations.RunPython(create_initial_teams, migrations.RunPython.noop)
]💡 Совет: Используйте
migrations.RunPython.noopвместоlambda x, y: None— это более читаемый и современный подход.
def create_default_category(apps, schema_editor):
Category = apps.get_model('shop', 'Category')
Category.objects.create(name='Uncategorized', slug='uncategorized')
def delete_default_category(apps, schema_editor):
Category = apps.get_model('shop', 'Category')
Category.objects.filter(slug='uncategorized').delete()
class Migration(migrations.Migration):
operations = [
migrations.RunPython(create_default_category, delete_default_category)
]Уже известная вам команда для применения миграции
Команда migrate применяется для выполнения миграций и синхронизации состояния базы данных с текущими моделями проекта.
python manage.py migrate [app_label] [migration_name]
-
app_label — имя приложения, миграции которого необходимо применить.
-
migration_name — имя конкретной миграции (обычно достаточно первых четырёх цифр из имени миграции).
Если не указать параметры, команда применит все миграции для всех приложений.
Допустим, вы уже применили миграции до 0008, но обнаружили ошибку в 0006. Вы можете откатить состояние базы данных
до миграции 0005:
python manage.py migrate my_app 0005
⚠️ Это приведёт к откату всех миграций после 0005 и, скорее всего, приведёт к потере данных, связанных с удаляемыми моделями и полями.
Флаг --fake позволяет пометить миграцию как выполненную, не внося реальные изменения в базу данных:
python manage.py migrate my_app 0005 --fakeЭтот приём используется в ситуациях, когда база данных уже была подготовлена вручную или через внешнюю систему (например переносите проект с другого фреймворка на джанго), и вы хотите синхронизировать Django с её текущим состоянием.
Если вместо номера миграции указать zero, можно откатить все миграции для указанного приложения:
python manage.py migrate my_app zero🔥 Важно: Откат миграций удалит таблицы и данные. Сделайте резервную копию!
Также уже известная вам команда, которая отобразит список миграций и их состояние (применена или нет).
Команда sqlmigrate позволяет посмотреть SQL-код, который будет сгенерирован и выполнен при применении определённой миграции. Это особенно полезно, если вы хотите:
- понять, какие структурные изменения будут внесены в базу;
- провести аудит миграций перед деплоем;
- получить SQL-запросы для ручного применения или изучения.
python manage.py sqlmigrate <app_label> <migration_name>app_label— имя приложения, напримерusers;migration_name— имя миграции без префикса0XXX_, достаточно просто0001илиinitial.
python manage.py sqlmigrate users 0001Вывод будет содержать SQL-запросы, необходимые для создания таблиц и индексов, например:
CREATE TABLE "users_user" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"username" varchar(150) NOT NULL UNIQUE,
"email" varchar(254) NOT NULL
);🔍 Совет: Используйте
sqlmigrate, чтобы убедиться, что миграции не создают неожиданных изменений, особенно если вы вносите правки вручную или используетеRunSQL.
Команда squashmigrations используется для объединения нескольких последовательных миграций в одну, чтобы упростить структуру миграций и улучшить читаемость проекта.
Это особенно полезно после активной разработки, когда в приложении накопилось множество миграций, добавляющих поля, изменяющих типы или удаляющих элементы в одной и той же модели.
python manage.py squashmigrations <app_label> <start_migration> <end_migration>Допустим, в приложении myapp имеются миграции 0004, 0005, 0006 и 0007, каждая из которых добавляет или изменяет поля в одной модели. Чтобы объединить их в одну миграцию, выполните:
python manage.py squashmigrations myapp 0004 0007Это создаст новую миграцию, содержащую совокупность всех изменений между 0004 и 0007, и заменит старые миграции.
Совет: squash-команды следует выполнять только после того, как все предыдущие миграции были применены на всех окружениях, иначе возможны проблемы с синхронизацией структуры базы данных.
- После нескольких итераций разработки модели, когда накопилось много миграций.
- Перед выпуском в продакшн, чтобы упростить аудит миграций.
- Для улучшения производительности применения миграций при CI/CD.
Squash не удаляет старые миграции автоматически — вы можете проверить и при необходимости удалить их вручную после тестирования новой объединённой миграции.
Команда flush очищает базу данных от всех данных пользователя, сбрасывая содержимое всех таблиц, но не отменяя миграций и не удаляя структуру базы.
Это значит, что после выполнения команды:
- Все таблицы останутся на месте.
- Все миграции будут считаться применёнными.
- Будут удалены все данные, включая созданных пользователей, записи моделей и данные сессий.
- Значения автоинкрементных полей (
id) будут сброшены (если это поддерживает СУБД).
python manage.py flushПосле выполнения вы получите предупреждение с подтверждением:
You have requested a flush of the database.
This will IRREVERSIBLY DESTROY all data in the database.
Are you sure you want to do this?- Для быстрого сброса данных при локальной разработке.
- При подготовке к демонстрации, когда нужна «чистая» база.
- Для очистки базы перед загрузкой фикстур:
python manage.py flush
python manage.py loaddata initial_data.json
⚠️ Важно: не используйтеflushв продакшене — она удалит все данные и их восстановление будет невозможно без резервной копии.
Команда sqlflush выводит SQL-инструкции, которые будут выполнены при вызове команды flush.
Это полезно, если вы хотите:
- Предварительно увидеть, какие SQL-команды будут применены;
- Понять, какие таблицы будут очищены;
- Использовать SQL напрямую (например, для предварительного анализа или отладки).
python manage.py sqlflush
$ python manage.py sqlflush
BEGIN;
DELETE FROM "myapp_modelname";
DELETE FROM "auth_user";
DELETE FROM "django_session";
...
COMMIT;- Команда не выполняет очистку базы данных, она только печатает SQL, который бы использовался.
- Учитывает текущую конфигурацию базы данных и зарегистрированные модели.
Команда sqlsequencereset используется для сброса последовательностей автогенерации идентификаторов (например, id) в базе данных. Это важно, когда вы вручную очищаете таблицы и хотите, чтобы следующая вставка начиналась с id = 1.
python manage.py sqlsequencereset <app_label><app_label>— имя приложения Django, для которого нужно сбросить последовательности.
Например:
python manage.py sqlsequencereset blogЭта команда не вносит изменений в базу напрямую, а просто печатает SQL-код, который необходимо выполнить для сброса последовательностей. Это особенно полезно при ручной очистке таблиц с помощью flush или truncate.
Когда вы удаляете все записи из таблицы вручную (например, через DELETE FROM), следующий id по умолчанию не будет начинаться с 1, так как значение хранится в последовательности (sequence). sqlsequencereset позволяет сбросить эти sequence-объекты, чтобы обеспечить консистентность ID.
SELECT setval(pg_get_serial_sequence('"myapp_mymodel"', 'id'), COALESCE(MAX("id"), 1), max("id") IS NOT null) FROM "myapp_mymodel";Этот SQL-запрос устанавливает значение последовательности id в зависимости от текущих данных в таблице.
Команда inspectdb используется для генерации моделей Django на основе уже существующей базы данных.
Это особенно полезно, если вы начинаете работать над проектом с существующей схемой базы данных, которая ранее не использовалась с Django. Вместо того чтобы вручную описывать модели, вы можете сгенерировать их автоматически.
python manage.py inspectdbВывод будет содержать Python-код с определениями моделей, которые можно скопировать в файл models.py соответствующего приложения.
Предположим, у вас есть существующая база данных с таблицами users и orders. Выполнив команду:
python manage.py inspectdb > myapp/models.pyВы получите файл models.py с автосгенерированными моделями:
class Users(models.Model):
id = models.AutoField(primary_key=True)
username = models.CharField(max_length=150)
email = models.EmailField()
class Meta:
managed = False
db_table = 'users'--database: имя подключения к базе данных, если используется не дефолтное (default).--include-partial: добавляет частично поддерживаемые поля.--table: позволяет указать конкретные таблицы для генерации моделей.
Пример для одной таблицы:
python manage.py inspectdb --table users- Генерируемые модели будут иметь
managed = False, что означает, что Django не будет пытаться мигрировать эти таблицы. - После генерации моделей их часто нужно вручную отредактировать: переименовать поля, добавить связи, ограничения и т. д.
- Это хороший старт, но не замена полноценного проектирования моделей вручную.
- При переносе существующего проекта (например, с PHP или Java) на Django.
- При необходимости подключиться к сторонней базе и использовать Django ORM без миграций.
- Для быстрого создания моделей для репорта или анализа на основе чужой БД.
Команда для смены пароля конкретному пользователю.
manage.py changepassword ringo
Команда для создания пользователя со всеми правами.
Команда shell запускает интерактивную Python-консоль с уже настроенной Django-средой. Это означает, что вам не нужно вручную импортировать модели, настройки и другие компоненты проекта — всё уже доступно.
Примеры использования:
python manage.py shellПолезно для:
- Ручного тестирования функций и моделей
- Временного взаимодействия с ORM
- Быстрого импорта и анализа данных
Команда dbshell открывает интерактивную консоль для работы с базой данных, указанной в настройках DATABASES.
Например, если вы используете PostgreSQL, будет открыт psql, если SQLite — обычная SQLite-консоль и т.д.
Примеры:
python manage.py dbshellПолезно для:
- Прямого выполнения SQL-запросов
- Быстрого анализа или модификации данных на низком уровне
- Отладки проблем с миграциями и схемой
Убедитесь, что нужный клиент базы данных установлен в системе (например,
psqlдля PostgreSQL,mysqlдля MySQL).
Команда runserver запускает встроенный отладочный сервер Django.
Пример:
python manage.py runserverМожно указать IP и порт:
python manage.py runserver 0.0.0.0:8000По умолчанию используется порт 8000 и адрес 127.0.0.1.
Полезно для:
- Разработки и локального тестирования приложения
- Быстрой демонстрации функциональности
⚠️ Важно:runserverне предназначен для продакшена. На боевых серверах используйте WSGI-серверы, такие как Gunicorn, uWSGI, Daphne и т. п.
Команда dumpdata используется для создания резервной копии данных из базы данных в виде JSON-файла (или в другом формате, если указать).
Это особенно полезно для:
- создания фикстур (fixtures), которые можно использовать для загрузки данных в тестовую или начальную среду;
- резервного копирования состояния отдельных приложений или моделей;
- миграции данных между проектами.
python manage.py dumpdata [app_label.ModelName] [--indent N] [--output filename.json]-
Сохранить все данные из базы:
python manage.py dumpdata > all_data.json -
Сохранить данные только из приложения
blog:python manage.py dumpdata blog > blog_data.json -
Сохранить данные из конкретной модели
Author:python manage.py dumpdata blog.Author > authors.json -
Сохранять с отступами (удобнее для чтения):
python manage.py dumpdata blog.Author --indent 2 > authors.json -
С указанием имени файла напрямую:
python manage.py dumpdata blog --output blog_data.json
Вы можете комбинировать команды с --exclude:
python manage.py dumpdata --exclude auth.permission --exclude contenttypes > data.jsonКоманда loaddata загружает данные из JSON-файла (или другого формата фикстур) обратно в базу данных.
Это особенно полезно для:
- восстановления состояния базы из резервной копии;
- начального наполнения базы данных (например, при запуске нового окружения);
- использования в тестах.
python manage.py loaddata filename.json-
Загрузка полного дампа:
python manage.py loaddata all_data.json
-
Загрузка дампа только моделей
Author:python manage.py loaddata authors.json
-
Загрузка нескольких файлов:
python manage.py loaddata file1.json file2.json
Фикстуры можно хранить в директориях fixtures/ в каждом приложении — Django их найдёт автоматически.
Важно: При загрузке данных из JSON-файла все
id, указанные в файле, будут использоваться как есть. Убедитесь, что данные не конфликтуют с уже существующими.
В Django команды makemessages и compilemessages используются для организации и управления локализацией (переводом интерфейса на разные языки).
Команда makemessages используется для создания или обновления файлов перевода (.po), которые содержат строки, подлежащие переводу.
python manage.py makemessages -l <locale> [опции]-l, --locale— язык перевода (например,fr,pt_BR,uk).--ignore— шаблон файлов, которые нужно игнорировать.--extension— расширения файлов, где следует искать переводы (по умолчанию.html,.txt,.py).--domain— можно указатьdjangojsдля перевода JS-файлов.
python manage.py makemessages -l ukЭтот вызов создаст (или обновит) файл перевода uk/LC_MESSAGES/django.po.
- В проектах с мультиязычным интерфейсом.
- При локализации текстов, сообщений и шаблонов.
- В любых Django-приложениях, где используется
gettext,ugettext, либо шаблонные теги{% trans "..." %}.
В views.py:
from django.utils.translation import gettext as _
def my_view(request):
output = _("Welcome to my site.")
return HttpResponse(output)В .po файле:
msgid "Welcome to my site."
msgstr "Ласкаво просимо на мій сайт."После редактирования .po файлов необходимо скомпилировать их в .mo файлы, которые используются Django во время выполнения.
python manage.py compilemessages [опции]python manage.py compilemessagesЭта команда пройдёт по всем подкаталогам и скомпилирует .po файлы в .mo файлы.
- После каждого изменения или добавления новых переводов.
- Перед сборкой и деплоем проекта, если используется мультиязычность.
# Обнаружение строк для перевода
python manage.py makemessages -l fr
# Перевод строк вручную в файле locale/fr/LC_MESSAGES/django.po
# Компиляция перевода
python manage.py compilemessagesВажно: Убедитесь, что в
settings.pyуказана настройкаLOCALE_PATHS, чтобы Django знал, где искать переводы:LOCALE_PATHS = [os.path.join(BASE_DIR, 'locale')]
В Django вы можете создавать собственные команды для manage.py, как и встроенные команды (migrate, runserver, makemigrations и т.д.). Это полезно для автоматизации задач: отчёты, массовые изменения в базе, уведомления и др.
Предположим, у нас есть проект пиццерии, и мы хотим, чтобы каждый день в 18:00 система проверяла заказы, которые остались в статусе «открыт», переводила их в статус «manual» и отправляла уведомление владельцу заведения.
Создаём команду close_orders внутри приложения orders:
orders/
├── __init__.py
├── models.py
├── management/
│ └── commands/
│ └── close_orders.py
├── tests.py
└── views.py
Папки
managementиcommandsдолжны быть именно с такими названиями.
from django.core.management.base import BaseCommand
from orders.models import Order
from orders.utils import send_email # примерная утилита отправки писем
class Command(BaseCommand):
help = "Close orders which weren't closed manually"
def handle(self, *args, **options):
orders = Order.objects.filter(status="opened")
if orders.exists():
orders.update(status="manual")
send_email(
subject="Unclosed orders",
message=f"You have {orders.count()} unclosed orders marked as 'manual'.",
to="owner@example.com"
)
self.stdout.write(self.style.SUCCESS("Successfully closed open orders."))
else:
self.stdout.write("No open orders to close.")Запустить команду можно вручную:
python manage.py close_ordersДобавьте в расписание cron выполнение этой команды каждый день в 18:00:
0 18 * * 1-5 /usr/bin/python3 /path/to/project/manage.py close_ordersУбедитесь, что активировано правильное виртуальное окружение, или используйте путь до интерпретатора прямо из него.
Создание кастомных manage.py команд — мощный способ автоматизировать задачи и расширить функциональность вашего проекта.
В этой лекции мы изучили инструменты Django для наблюдаемости, обработки запросов и автоматизации:
| Инструмент | Назначение | Когда использовать |
|---|---|---|
| Logging | Запись информации о работе приложения | Отладка, мониторинг, аудит |
| Middleware | Сквозная обработка request/response | Аутентификация, timing, CORS, rate limiting |
| Signals | Реакция на события без изменения кода модели | Уведомления, аудит, инвалидация кеша |
| Background Tasks | Выполнение кода вне цикла запроса | Email, обработка файлов, внешние API |
| Messages | Одноразовые уведомления пользователю | Flash-сообщения после redirect |
| Manage-команды | Скрипты для автоматизации | Cron-задачи, импорт данных, обслуживание |
- Логирование — используйте JSON-формат в production, интегрируйте с Sentry/ELK
- Middleware — порядок важен! Не размещайте тяжёлую логику
- Signals — используйте
dispatch_uid, выносите тяжёлые операции в очереди - Background Tasks — передавайте ID объектов, не сами объекты; используйте
transaction.on_commit - Messages — не забывайте про
MessageMiddlewareи context processor - Data Migrations — используйте
RunPython.noopдля обратных миграций
-
Настройте логирование в вашем проекте:
- Создайте отдельный логгер для вашего приложения
- Настройте вывод в файл с форматом
verbose - Добавьте логирование в одну из view (начало обработки, результат, ошибки)
-
Создайте middleware для логирования времени выполнения запросов:
- Логируйте метод, путь и время в миллисекундах
- Добавьте заголовок
X-Request-Durationв ответ
-
Добавьте flash-сообщения в форму регистрации или создания объекта:
messages.successпри успешном созданииmessages.errorпри ошибке валидации
-
Создайте сигнал для аудита изменений модели:
- При сохранении объекта логируйте, кто и что изменил
- Используйте
dispatch_uid - Зарегистрируйте сигнал в
apps.py
-
Напишите manage-команду для вашего проекта:
- Команда должна принимать аргументы (например,
--days=7) - Выводите результат с использованием
self.style.SUCCESS/ERROR - Пример: очистка старых записей, генерация отчёта, отправка напоминаний
- Команда должна принимать аргументы (например,
-
Создайте Data Migration:
- Добавьте начальные данные для справочника (категории, статусы, роли)
- Используйте
RunPython.noopдля обратной миграции
-
Реализуйте Request ID middleware:
- Генерируйте UUID для каждого запроса
- Добавляйте его в логи через
logging.Filter - Возвращайте в заголовке ответа
-
Интегрируйте сигналы с Background Tasks (Django 6.0+):
- При создании заказа ставьте задачу отправки email в очередь
- Используйте
transaction.on_commit
-
В каком порядке вызываются middleware при обработке запроса и ответа?
-
Почему сигналы не вызываются при использовании
QuerySet.update()? -
Чем
RunPython.noopлучшеlambda x, y: None? -
Когда лучше использовать переопределение
save()вместо сигналаpost_save? -
Почему важно использовать
transaction.on_commitпри постановке задач в очередь? -
Какой backend для Background Tasks использовать в production?
-
Где правильно регистрировать сигналы и почему?
-
Как добавить контекст (user_id, request_id) ко всем логам в рамках одного запроса?
← Лекция 25: NoSQL. Куки, сессии, кеш | Лекция 27: Что такое API. REST и RESTful. Django REST Framework. →

