Django — веб-фреймворк на Python
Django — мощный высокоуровневый веб-фреймворк на Python. Он «из коробки» даёт админку, ORM, аутентификацию, формы и безопасность, поэтому на нём строят интернет-магазины, порталы и сервисы. Этот курс проведёт тебя от установки до продакшена.
1. Что такое Django и паттерн MTV
Django появился в 2005 году как внутренний инструмент новостного сайта и стал одним из самых популярных фреймворков. Его идеология — «batteries included»: вместо десятка отдельных библиотек ты получаешь всё необходимое сразу, включая инструменты для создания сайтов и API.
Архитектура Django основана на паттерне MTV (Model-Template-View):
- Model — описание данных: модели соответствуют таблицам базы данных.
- View — Python-функция или класс, который обрабатывает запрос и готовит данные (по сути «контроллер»).
- Template — HTML-шаблон, куда подставляются данные.
Плюс третий «невидимый» элемент — URLconf: таблица соответствий «адрес страницы → view».
Запрос → URLconf → View (Python) → Model (база данных)
↕ ↕
Template (HTML) ← данных подставлены2. Установка и создание проекта
Django ставится через pip. Работать лучше в виртуальном окружении, чтобы не засорять системный Python.
# Windows (PowerShell)
python -m venv .venv
.venv\Scripts\activate # активация окружения
pip install django
# Linux / macOS
python3 -m venv .venv
source .venv/bin/activate
pip install djangodjango-admin startproject mysite
cd mysite
python manage.py runserverОткрой http://127.0.0.1:8000 — увидишь стартовую страницу Django. Команда runserver — встроенный сервер для разработки.
Структура проекта
| Файл | Назначение |
|---|---|
| manage.py | главная точка входа всех команд |
| mysite/settings.py | настройки: база, приложения, язык |
| mysite/urls.py | корневой URLconf |
| db.sqlite3 | база данных по умолчанию (SQLite) |
3. Приложения Django
Проект — это целый сайт, а приложение — самостоятельный модуль внутри него (блог, каталог, корзина). Приложения можно переиспользовать между проектами как готовые блоки.
python manage.py startapp shop
Зарегистрируй новое приложение в файле настроек:
# mysite/settings.py
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
...
"shop", # наше приложение
]Что создаёт startapp
| Файл | Назначение |
|---|---|
| models.py | описание моделей (таблиц) |
| views.py | представления — логика страниц |
| urls.py | свой URLconf приложения (файл создаётся вручную) |
| admin.py | регистрация моделей в админке |
| migrations/ | история изменений структуры базы |
| tests.py | автотесты |
4. Модели и миграции
Модель в Django — это класс Python, который описывает таблицу в базе данных. Поля — столбцы таблицы.
# shop/models.py
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100)
class Product(models.Model):
name = models.CharField(max_length=200)
price = models.DecimalField(max_digits=8, decimal_places=2)
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name="products",
)
in_stock = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.nameТеперь нужно создать и применить миграции — Django сам сгенерирует SQL на основе моделей:
python manage.py makemigrations
python manage.py migrateОсновные типы полей
| Поле | Что хранит |
|---|---|
| CharField | короткая строка (до max_length) |
| TextField | длинный текст |
| IntegerField | целое число |
| DecimalField | деньги с точной точностью |
| DateTimeField | дата и время |
| BooleanField | True или False |
| ForeignKey | связь «многие к одному» |
5. Админка Django
Django автоматически даёт готовый административный интерфейс. Нужно лишь создать суперпользователя и зарегистрировать модели.
python manage.py createsuperuser
# введи логин, email и пароль# shop/admin.py
from django.contrib import admin
from .models import Category, Product
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ("name",)
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ("name", "price", "category", "in_stock")
list_filter = ("category", "in_stock")
search_fields = ("name",)Запусти сервер и открой http://127.0.0.1:8000/admin. Войди под суперпользователем — уже можно добавлять категории и товары через удобный интерфейс без единой строки представлений.
# для тонкой настройки списка можно добавить кастомные поля
class ProductAdmin(admin.ModelAdmin):
list_display = ("name", "price", "sale_price", "in_stock")
@admin.display(description="Цена со скидкой")
def sale_price(self, obj):
return f"{obj.price * 0.9:.2f}"6. ORM-запросы
ORM (Object-Relational Mapping) позволяет работать с базой как с Python-объектами, не написав ни строчки SQL. Запросы выполняются в Django shell.
python manage.py shell# создать объект
c = Category.objects.create(name="Обувь")
# массовое создание
Product.objects.create(name="Кроссовки", price="4990.00", category=c)
Product.objects.create(name="Ботинки", price="6990.00", category=c)
# выборка
Product.objects.all() # все
Product.objects.filter(price__gt=5000) # дороже 5000
Product.objects.exclude(in_stock=False) # кроме распроданных
Product.objects.order_by("-price") # по убыванию цены
Product.objects.count() # количествоСинтаксис условии строится через двойное подчёркивание: price__lt, name__icontains, created__year.
# фильтры через связи
obuv = Category.objects.get(name="Обувь")
obuv.products.all() # товары категории (related_name)
Product.objects.filter(category__name="Обувь") # через связь
# агрегация
from django.db.models import Avg, Sum
Product.objects.aggregate(avg=Avg("price"), total=Sum("price"))SELECT-логирование или инструменты вроде django-debug-toolbar. Проблему N+1 (запрос на каждый объект в цикле) решают через select_related и prefetch_related.7. Представления и URL
Представление (view) получает запрос, берёт данные из базы и возвращает ответ. Связь URL с представлением задаётся в URLconf.
# shop/views.py
from django.shortcuts import render, get_object_or_404
from .models import Product, Category
def home(request):
products = Product.objects.filter(in_stock=True)[:8]
return render(request, "shop/home.html", {"products": products})
def product_detail(request, pk):
product = get_object_or_404(Product, pk=pk)
return render(request, "shop/detail.html", {"product": product})
def by_category(request, category_id):
category = get_object_or_404(Category, pk=category_id)
products = category.products.all()
return render(request, "shop/catalog.html", {
"category": category,
"products": products,
})# shop/urls.py — создаём вручную
from django.urls import path
from . import views
urlpatterns = [
path("", views.home, name="home"),
path("product/<int:pk>/", views.product_detail, name="product-detail"),
path("category/<int:category_id>/", views.by_category, name="by-category"),
]# mysite/urls.py — подключаем приложение
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("shop.urls")),
]Конвертеры URL
| Конвертер | Пример URL | Тип в view |
|---|---|---|
| <int:pk> | /product/42/ | int |
| <str:name> | /category/sport/ | str |
| <slug:slug> | /news/kak-rabotaet-django/ | str (slugs) |
| <path:rest> | /files/a/b/c.txt | str с разделителями |
8. Шаблоны
Шаблоны Django — это HTML с тегами подстановки данных. Файлы шаблонов по умолчанию Django ищет в папке templates внутри приложения.
<!-- shop/templates/shop/catalog.html -->
<!DOCTYPE html>
<html lang="ru">
<head>
<title>{{ category.name }} — магазин</title>
</head>
<body>
<h1>Категория: {{ category.name }}</h1>
{% for product in products %}
<div>
<a href="{% url 'product-detail' product.pk %}">
{{ product.name }}
</a> — {{ product.price }} руб.
{% if not product.in_stock %}
<b>(нет в наличии)</b>
{% endif %}
</div>
{% empty %}
<p>Товаров пока нет.</p>
{% endfor %}
</body>
</html>Основные теги шаблонов
| Синтаксис | Назначение |
|---|---|
| {{ переменная }} | вывод значения |
| {% if %} | условие |
| {% for %} | цикл по списку |
| {% url 'name' %} | ссылка по имени маршрута |
| {% include %} | вставка другого шаблона |
| {% extends %} | наследование базового шаблона |
| {{ value|upper }} | фильтр (upper, length, date) |
<!-- shop/templates/base.html — общий каркас -->
<html lang="ru">
<head><title>{% block title %}Магазин{% endblock %}</title></head>
<body>
<header>Магазин{% if user.is_authenticated %} · {{ user.username }}{% endif %}</header>
<main>{% block content %}{% endblock %}</main>
</body>
</html>
<!-- страница-потомок: -->
{% extends "base.html" %}
{% block content %}
<h1>Категория: {{ category.name }}</h1>
<!-- содержимое страницы -->
{% endblock %}9. Формы и аутентификация
Django умеет строить формы из моделей и валидировать данные. Аутентификация уже встроена: регистрация, вход, выход — пара строк кода.
# shop/forms.py
from django import forms
from .models import Product
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ["name", "price", "category", "in_stock"]
widgets = {
"name": forms.TextInput(attrs={"class": "form-input"}),
}# shop/views.py — обработка формы
from django.shortcuts import render, redirect
from .forms import ProductForm
def product_create(request):
if request.method == "POST":
form = ProductForm(request.POST)
if form.is_valid():
product = form.save()
return redirect("product-detail", pk=product.pk)
else:
form = ProductForm()
return render(request, "shop/form.html", {"form": form})<!-- шаблон формы с выводом ошибок -->
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Сохранить</button>
</form>Аутентификация подключается готовыми маршрутами:
# mysite/urls.py
from django.contrib.auth import views as auth_views
urlpatterns = [
path("login/", auth_views.LoginView.as_view(), name="login"),
path("logout/", auth_views.LogoutView.as_view(), name="logout"),
]Права в представлениях проверяются декораторами: @login_required — только для авторизованных, @user_passes_test — по условию.
from django.contrib.auth.decorators import login_required
@login_required
def profile(request):
return render(request, "shop/profile.html", {"user": request.user})10. Статика, медиа и продакшен
CSS, JS и картинки интерфейса — это статические файлы. Они складываются в STATIC и собираются командой collectstatic перед деплоем. Файлы, загружаемые пользователями, — это медиа (MEDIA_ROOT, MEDIA_URL).
# mysite/settings.py
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
MEDIA_URL = "media/"
MEDIA_ROOT = BASE_DIR / "media"<!-- в шаблоне -->
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">Перед деплоем нужно выключить режим отладки, собрать статику и запустить gunicorn:
# mysite/settings.py
DEBUG = False
ALLOWED_HOSTS = ["mydomain.ru"]python manage.py collectstatic --noinput
pip install gunicorn # WSGI-сервер (Linux)
gunicorn mysite.wsgi:application --bind 0.0.0.0:8000# конфиг Nginx: отдача статики и прокси на gunicorn
location /static/ { root /home/me/mysite/; }
location /media/ { root /home/me/mysite/; }
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
}11. Кэширование в Redis
Redis — быстрая in-memory база, стандарт для кэша Django. Устанавливаем байнд:
pip install django-redisCACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
}
}Кэшируем представления или куски шаблонов:
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # 15 минут для анонимных запросов
def catalog(request): ...{% load cache %}
{% cache 300 latest_news %}
... тяжелая выборка новостей ...
{% endcache %}python manage.py cache_clear или инвалидация по ключам.12. Фоновые задачи: Celery
Долгие операции (отправка письма, генерация PDF, разбор загрузки) выносят из HTTP-запроса в очередь. Celery + Redis — распространённая пара:
pip install celery# tasks.py
from celery import shared_task
@shared_task
def send_welcome_email(user_id):
user = User.objects.get(pk=user_id)
send_mail("Добро пожаловать", "…", user.email)def register(request):
user = User.objects.create_user(...)
send_welcome_email.delay(user.id) # в очередь, не блокирует ответ
return render(request, "done.html")celery -A mysite worker -l info # запуск воркера| Приём | Зачем |
|---|---|
| .delay() | отправить задачу в очередь |
| task.retry | повторить при временной ошибке |
| Celery Beat | периодические задачи (рассылки, отчёты) |
| Result backend | сохранить статус/результат задачи |
13. Тестирование: pytest-django
Тесты защищают от регрессий при росте проекта:
pip install pytest pytest-django# tests/test_orders.py
import pytest
from django.urls import reverse
from .models import Product
@pytest.mark.django_db
def test_catalog_page_shows_products(client):
Product.objects.create(title="Ноутбук", price=1000)
resp = client.get(reverse("catalog"))
assert resp.status_code == 200
assert "Ноутбук" in resp.content.decode()- fixture
client— имитирует HTTP-запросы; - метка
django_db— доступ к тестовой БД; pytest.raises— проверка исключений;- CRUD-тесты для моделей и форм — самый частый случай покрытия.
14. Безопасность и хардненинг
Базовая защита Django включается настройками и маленькими привычками:
DEBUG = Falseв проде — обязательно;- секреты и ключи — в переменных окружения;
SECURE_SSL_REDIRECT, HSTS, Content-Security-Policy;- регулярно обновляй зависимости;
- защита CSRF и валидация форм уже встроены — не отключай.
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 3153600015. Продвинутые миграции
Миграции меняют не только схему, но и сами данные. Data migration выполняется через RunPython:
def uppercase_names(apps, schema_editor):
Product = apps.get_model("shop", "Product")
for p in Product.objects.all():
p.title = p.title.title()
p.save()
class Migration(migrations.Migration):
dependencies = [("shop", "0002_alter_product_price")]
operations = [migrations.RunPython(uppercase_names)]- переименуй поле через
RenameField— так история миграций остаётся понятной; - объединяй накопившиеся миграции через
python manage.py squashmigrations; - большие переносы данных выполняй в бэкграунд-задаче, а не внутри одной миграции.
python manage.py squashmigrations shop 000516. REST API на Django REST Framework
DRF превращает модели в API за минуты: сериализатор описывает поля, viewsets — CRUD-действия:
# serializers.py
from rest_framework import serializers
from .models import Product
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ["id", "title", "price"]# views.py
from rest_framework.viewsets import ModelViewSet
from .serializers import ProductSerializer
class ProductViewSet(ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer- смарт набор API присутствует через rut-роутер:
/products/,/products/1/; - пагинация, фильтры и аутентификация настраиваются одним классом;
- документация Swagger/OpenAPI — через
drf-spectacular.
select_related/prefetch_related в queryset сериализатора спасает API от медленных ответов.17. Кастомные теги шаблонов
Когда штатного синтаксиса шаблонов не хватает, пиши свой тег в templatetags/:
# shop/templatetags/money.py
from django import template
register = template.Library()
@register.simple_tag
def format_price(value):
return f"{value:,.2f} ₽".replace(",", " ")# в шаблоне
{% load money %}
<span>Цена: {% format_price product.price %}</span>simple_tag— простые функции;inclusion_tagрендерит целый фрагмент;- контекстные процессоры добавляют переменные для всех шаблонов (например, корзину);
- теги должны работать быстро — они вызываются при каждом рендере.
# settings.py
"context_processors": [
"cart.context_processors.cart_total",
]18. Логирование и мониторинг
Продакшен без логов — «чёрный ящик». Настройка через LOGGING в settings:
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"handlers": {"console": {"class": "logging.StreamHandler"}},
"root": {"handlers": ["console"], "level": "INFO"},
}- структурируй: время, уровень, logger, сообщение — так удобно фильтровать в ELK;
- собирай исключения в Sentry — начни с
sentry_sdk.init(dsn=...); - health-check эндпоинт
/healthz/отдаёт статус БД и кэша для балансировщика.
# Sentry в entrypoint-коде
import sentry_sdk
sentry_sdk.init(dsn="https://...@sentry.io/1", traces_sample_rate=0.1)19. Веб-сокеты: Django Channels
Долгие обновления «в лоб» короткими запросами — боль. Channels добавляет WebSocket-слой поверх Django:
# consumers.py
class ChatConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.channel_layer.group_add("room", self.channel_name)
await self.accept()
async def receive(self, text_data):
await self.channel_layer.group_send(
"room", {"type": "msg", "data": text_data})- ASGI вместо WSGI:
uvicorn/daphne; - channel_layer — Redis в проде, in-memory для разработки;
- защити
connect: проверяй авторизацию доaccept().
20. Поиск и фильтры: Q-объекты и django-filter
Фильтры и поиск быстро превращают ORM-код в «стену if». Ставь на библиотеки:
class ProductFilter(filters.FilterSet):
name = filters.CharFilter(lookup_expr="icontains")
price_min = filters.NumberFilter(field_name="price", lookup_expr="gte")Q-объекты связывают условия&,|,~без цепочек if;- полнотекстовый поиск в PostgreSQL:
SearchVector/SearchQuery; - для автокомплита —
icontains+ индекс, а не сканирование всей таблицы.
qs = Product.objects.filter(Q(category="books") | Q(tags__name=term))21. Миграции данных: RunPython
Данные эволюционируют вместе со схемой. Перенос значений — через data-миграцию:
def update_slugs(apps, schema_editor):
Product = apps.get_model("shop", "Product")
for p in Product.objects.all():
p.slug = slugify(p.name)
p.save(update_fields=["slug"])
operations = [
migrations.RunPython(update_slugs,
reverse_code=migrations.RunPython.noop),
]- используй исторические модели
apps.get_model, а не свои импорты; - большие таблицы обрабатывай батчами
iterator(chunk_size=2000); - всегда пиши обратный код — откат, который не страшно проиграть.
22. Оркестр запросов: prefetch и индексы
Медленный Django-сайт в 90% случаев — N+1 и отсутствующие индексы:
products = Product.objects \
.select_related("category") \
.prefetch_related(Prefetch("reviews",
queryset=Review.objects.order_by("-created")))- вырезай лишние колонки:
only()/values(); - индексы под JOIN и фильтры:
Meta.indexes+F-выражения; - диагностируй через
connection.queriesи django-debug-toolbar.
with connection.cursor() as c:
c.execute("EXPLAIN ANALYZE SELECT ...")Что дальше
Ты освоил путь от «Привет, мир» до рабочего сайта: модели, админку, ORM, представления, шаблоны, формы, аутентификацию и продакшен. Для роста попробуй REST API для Django по курсу REST API, освежи синтаксис в Python и разберись с базами в SQL. Деплой в деталях — в Хостинг и деплой.