/
Текст
Изучаем
Django
с нуля до полноценного
приложения
context)
django
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
context = {
'message': 'Привет, Django!'
}
return render(request, 'index.html',
ПРАКТИЧЕСКИЕ
ПРИМЕРЫ
ПОШАГОВЫЕ
ИНСТРУКЦИИ
РАБОТА
С БАЗОЙ ДАННЫХ
ГОТОВОЕ
ПРИЛОЖЕНИЕ
Изучаем Django: с
нуля до
полноценного
приложения
Санкт-Петербург
«ЛКМНН»
2026
Изучаем Django: с нуля до
полноценного приложения
Оглавление книги
Изучаем Django: с нуля до полноценного приложения
Санкт-11етербурт
Для кого эта книга
Предварительные требования [знание Python)
Что такое Django и почему он гак популярен
Обзор структуры книги
1.1, Виртуальные окружения в Python: зачем нужны и как создать (venv)
1.2. Установка Django через pip
1.3. Команда django-admin startproject: создание каркаса проекта
1.4, Подробный разбор структуры файлов
1.5. Первый ыпуск встроенного сервера р.1 зраоотки (runserver)
2.1. Концепция проектов и приложений | Project vs Арр) в Django
2.2. Создание первого приложения с помощью startapp
2.3. Регистрация приложения в INSTALLED APPS
2.4. Создание первого представления | View): ф\ нкция п HttpResponse
2.б. Настройка маршрутов | urls.py) на у ровне проекта и приложения
3.1, Как Django рабочиеi с HTML (встроенный шдблонизатор DTL)
Философия DTL: Ра тделепие логики и представления
Зашита от XSS (Cross-Site Scripting)
3.2. Настройка папок для шаблонов и использование функции renderl)
Массив TEMPLATES в settings.ру
Пространства имен (Namespacing) шаблонов
Функция rendert)
3.3. Передача контекста (context) из представления в шаблон
Пример передачи простыл данных
Передача сложных структур данных
3.4, Базовый синтаксис шаблонов: переменные, теш (if. for) и фильтры
Переменные: {{ variable)}
Tei и шаблонов: (% tag %}
Фильтры: | (найпы)
3.5. Наследование шаблонов: DRY в действии (extends и block)
Создание базового шаблона ( base.html)
Создание дочернего шаблона (extends)
Специфика {{ block.super }} и тег include
3.6. Подключение статических файлов (CSS, javaScript, изображения)
Нас1 ройка settings.ру щтя с итики
Тег {% load static %}
Итоги 3 главт.1
4,1, Введение в ORM [Object-Relational Mapping]
Что такое ORM?
Преимушес'ва использования Django ORM;
4.2. Настройка базы данных [использование SQLite ио умолчанию )
Словарь DATABASES
Почем) SQLite?
4.3. Создание моделей: классы и типы полей
Написание класса модели
Разбор типов полей (Field Types]
Параметры полей (Field Options]
Метаданные класса и магический метод str
4.4, Связи между таблицами
Один-ко Многим [Une to Many]: ForeignKey
Многие-ко-Многим (Many-to-Many): ManyToManyField
Один-к-Одной (One-to-One): OneToOneField
4.5. Миграции: подготовка (makemigrations] и применение [migrate]
Шаг 1: Подютовка миграций | makemigrations]
Шт 2: Применение миграций (migrate]
4.6. Интерактивная оболочка Django shell и базовые запросы к БД (CRUD)
CRUD операции (Create, Read, Update, Delete]
4.7. Bei роенная панель администратора: решсграция моделей
Создание суперпользователя
Pei иеграция модулей в admin.py
Кастомизация админки [ModelAdmin]
Итоги 4 главы
5.1. HTML-формы; сложности и подводные камни
Анатомия HTML-формы
GET против POST
Проблемы ручной обработки
5.2. Использование классов Form в Django
Создание файла forms.py
Разбор полей формы
Бидже гы [Widgets]
5.3. Огрисовка форм в шаблоне и защита от CSRF
Передача формы из предс тавления
Рендеринг в шаблоне (contact.html)
Что такое (% csrf token %] и зачем он нужен?
5.4. Обработка POST-запросов и ва шдацня данных
Паттерн обработки формы
Валидация (is valid) | и cleaned data]
Кастомная валидация
Паттерн Post/Redirect/Get
5.5. Формы на основе моделей (ModelForm); быстрое сохранение данных в БД
Mai ия ModelForm
Сохранение данных: метод savef]
Редактирование существующих объектов
Метод save(commit=False]
Итоги 5 1лавы
6.1. Разница между функциями-представлениями fFBV] и классами-представлениями
(CBV)
Функции-представления (FBV): Простота и явность
Классы-представления [CBVJ: Сила наследования и модульности
Маршру шзация для CBV
6.2. Использование встроенных generic views: TemplateView, ListView, DerailView
1, TemplateView: Отображение статических страниц
2. ListView: Вывод списков данных [катало! и, блош]
3. DetailView: Детальная страница объект
6.3. Формы и редактирование через CreateView, UpdateView, DeleteView
1. CreateView: Создание новых записей
2. UpdateView: Редактирование существующих записей
3. DeleteView: Удаление записей
6.4, Иереопре юление методов (get context data, get queryset, form valid)
1, Метод get querysetl.1:. (инамнческая фильтрация данных
2. Метл get context data)]: Передача дополнительных переменных в шаблон
3. Метод form validf): Перехват успешной отправки формы
Итоги б тлавы
7,1, Встроенная система аутентификации Django
Приложение django.contrib.auth
7.2. Создание форм регистрации, входа [login] и выхода (logout]
Регистрация: UserCreationForm
Вход (Login): Встроенный LoginView
Выход I Logout): Встроенный LogoutView
Отображение сос гояния в шаблоне
7.3. Управление сессиями
Как это работает под капотом
7.4. Разграничение доступа: декораторы и миксины
Защита FBV: декоратор (Blogin required
Защита CBV: LoginRequiredMixin
Проверка авторства fUserPassesTestMixin]
Итоги 7 главы
8.1. Зачем писать тесты: введение в TDD
Что такое TDD?
8,2, Встроенный фреймворк тестирования Django
Файл tests.py и класс TestCa.se
8.3. Написание тестов для моделей и представлений
Тестирование моделей
Тестирование представлений (Views]
8.4, Запуск тестов и анализ результатов
Команда test
Чтение консольного вывода
Покрытие кода тестами (Coyerage)
Итоги 8 тлавы
9.1, Разница между сервером разработки и продакшен-сервером
9.2. По тготовка настроек: DEBUG. ALLOWED HOSTS и SECRET KEY
Переменные окружения I Environment Variables]
Почему DEBUG = False так важен?
ALLOWED HOSTS
9.3. Настройка базы данных для поодакшена fPostgreSOL]
Пакет dj-database-url
9.4. Работа со статикой в продакшене (WhiteNoise]
Команда collectstatic
Использование WhiteNoise
9.5, Варианты хостшпа: пример развертывания
Иодтоговка файла зависимостей (requirements.txt]
С крипт сборки (build.sh]
Настройка Gunicorn
Деттлой на Render (краткий об тор)
Заключение
Введение
Для кого эта книга
Эта книга предназначена для гех, кто хочет научиться создавать современные
веб-приложения, используя один из самых мощных и популярных фреймворков — Django.
Если вы уже знакомы с основами программирования и хотите перейти к веб-разработке, это
руководство шаг за шагом проведет вас от первой строчки кода до рабочего сайта на
реальном сервере.
Предварительные требования (знание Python)
Мы предполагаем, что вы уже имеете базовое представление о языке Python. Вам не нужно
быть экспертом, но вы должны понимать, как работают переменные, типы данных, циклы,
функции и иметь хотя бы начальное представление об объектно-ориентированном
программировании (классах и объектах). Знание HTML и CSS на базовом уровне также будет
большим плюсом, но мы будем пояснять основные моменты по ходу дела.
Что такое Django и почему он так популярен
Django — это высокоуровневый веб-фреймворк на Python, который поощряет быструю
разработку и чистый, прагматичный дизайн. Его главная философия — «Включены все
батарейки» (Batteries included). Это означает, что прямо из коробки вы получаете
множество готовых инструментов: систему аутентификации, функциональную панель
администратора, удобные средства для работы с базами данных (ORM) и механизмы защиты
от уязвимостей. Он избавляет разработчика от рутины, позволяя сосредоточиться на
написании уникального функционала приложения.
Обзор структуры книги
Книга построена по принципу «от простого к сложному». Мы начнем с подготовки рабочего
окружения и создания пустого проекта. Затем научимся создавать приложения, настраивать
маршрутизацию и работать с шаблонами. В середине книги мы глубоко погрузимся в базы
данных, формы и систему пользователей. Завершим наш путь изучением тестирования и
развертыванием готового проекта в интернете.
Глава 1: Подготовка рабочего
окружения и создание первого
проекта
1.1. Виртуальные окружения в Python: зачем нужны и как создать
(venv)
При разработке на Python хорошей практикой считается использование виртуальных
окружений. Виртуальное окружение — это изолированная среда, в которой хранятся
зависимости (библиотеки) конкретного проекта. Это позволяет избежать конфликтов версий
библиотек между разными проектами на вашем компьютере. Например, если одному
проекту нужен Django 3.2, а другому — Django 5 0, виртуальные окружения решат эту
проблему.
Для создания виртуального окружения откройте терминал (или командную строку),
создайте папку для вашего проекта, перейдите в нее и выполните следующую команду:
python -m venv venv
После этого окружение нужно активировать.
На Windows'
venv\Scripts\activate
На macOS и Linux:
source venv/bin/activate
1.2. Установка Django через pip
Убедившись, что виртуальное окружение активировано (обычно в терминале появляется
префикс "(venv)"), мы можем установить фреймворк с помощью встроенного пакетною
менеджера pip:
pip install django
Вы можете проверить, успешно ли установлена библиотека и узнать её версию, выполнив
команду:
python -m django --version
1.3. Команда django-admin startproject: создание каркаса проекта
Теперь мы готовы создать наш первый проект. В Django есть специальная утилита для
генерации шаблонного кода проекта — django-admin. Выполните команду:
django-admin startproject myproject .
Обратите внимание на точку в конце команды. Она указывает Django создать файлы проекта
в текущей директории, а не плодить дополнительные вложенные папки.
1.4. Подробный разбор структуры файлов
После выполнения команды в вашей папке появятся новые файлы. Давайте разберем, за что
отвечает каждый из них (manage.py, settings.py, urls.py, asgi.py, wsgi.py):
• manage.py: Это главная утилита командной строки, которая позволяет вам
взаимодействовать с этим Django проектом (например, запускать сервер, создавать таблицы
в бате данных или запускать тесты).
• myproject/: Это папка конфигурации вашего проекта.
• settings.py: Главный файл настроек. Здесь хранятся доступы к базе данных,
подключенные приложения, настройки локализации, безопасности и пути к статическим
файлам.
• urls.py. Маршрутизатор (диспетчер URL). Здесь вы указываете, какой код (предст авление)
должен выполняться при переходе по определенному адресу в браузере.
• asgi.py и wsgi.py: Точки входа для веб-серверов. Они понадобятся нам в самом конце
книги, когда мы будем разворачивать проект (Deployment) на реальном хостинге для связи
приложения с серверами вроде Gunicorn или Uvicorn.
1.5. Первый запуск встроенного сервера разработки (runserver)
Django включает в себя легковесный веб-сервер, который отлично подходит для разработки
и тестирования приложения на вашем компьютере. Чтобы запустить его, выполните
команду в гой же папке, где находится файл manage.py
python manage.ру runserver
В терминале появится сообщение о том, что сервер запущен, обычно но локальному адресу
http://127.0.0.1:8000/. Откройте этот адрес в вашем браузере. Если всё сделано правильно,
вы увидите стартовую страницу Django с ракетой и поздравлением об успешной установке!
Ваш первый шаг в мир Django успешно сделан.
Глава 2: Приложения (Apps) и
маршрутизация (URL Routing)
2.1. Концепция проектом и приложений (Project vs Арр) в Django
Одной из ключевых особенностей архитектуры Django является разделение на «проекты»
(projects) и «приложения» (apps). Важно сразу понять разницу между ними.
Проект — что весь ваш веб-сайт целиком. Он содержит глобальные настройки
(подключение к базе данных, язык, часовой пояс) и объединяет все компоненты воедино.
Приложение — это отдельный модуль, который выполняет конкретную функцию.
Например, система комментариев, блог, форум или каталог товаров — всё это отдельные
приложения.
Такой подход позволяет делать приложения независимыми. Написав приложение для блога
один раз, вы сможете легко перенести его в свой следующий проект без лишней боли.
2.2. Создание первого приложения с помощью startsрр
Давайте создадим наше первое приложение. Убедитесь, что вы находитесь в папке проекта
(там, где лежит файл manage.py) и ваше виртуальное окружение активировано. Введите в
терминале команду:
python manage.py startapp blog
В результате Django создаст новую папку blog со своей структурой файлов:
• views.py Здесь мы будем писать логику обработки запросов (представления).
• models.ру: Файл для описания структуры базы данных (моделей).
• admin.ру: Настройки для панели администратора.
• apps.py: Файл конфигурации самого приложения.
2.3. Регистрация приложения в INSTALLED_APPS
Хотя папка с приложением создана, наш проект (myproject) пока о ней ничего не знает.
Чтобы Django начал работать с новым приложением, его нужно зарегистрировать.
Откройте файл settings.py в папке myproject и найдите список INSTALLED APPS. Добавьте
название нашего приложения в конец списка:
INSTALLED APPS = [
'django contrib.admin',
' djangc. contrib. au th ‘ ,
'django.contrib.contenttypes',
'django.contrib.sessions',
'django contrib messages',
'django.contrib.staticfiles',
'blog', # Наше новое приложение
]
Теперь проект официально включает в себя приложение blog.
2.4. Создание первого представления (View): функция и HttpResponse
Представление (View) в Django — это обычная функция (или класс) Python, которая
принимает веб-запрос (Web request) и возвращает веб-ответ (Web response). Оз вег может
быть HTML-страницей, перенаправлением, ошибкой 404 или XML-документом. Но пока мы
начнем с простого текста.
Откройте файл blog/views.ру и напишите следующий код:
from django.http import HttpResponse
def home(request).
return HttpResponse("Привет, мир! Это мое первое
D j ango-прилож ение ")
Мы импортировали класс HttpResponse и создали функцию home. Она принимает объект
request (информацию о HTTP-запросе) и возвращает объект HttpResponse с текстом.
2.5. Настройка маршрутов (urls.py) на уровне проекта и приложения
Теперь нам нужно связать URL-адрес с нашей функцией home. Для этого используется
маршрутизация (URL routing). Хорошей практикой считается хранить URL-адреса каждого
приложения внутри самою приложения.
Шаг 1: Создайте файл urls.py внутри папки приложения blog и добавьте в него следующий
код:
from django urls import path
from . import views
urlpatterns = [
path('', views.home, name='blog-home'),
]
Здесь мы говорим: если пользователь заходит по пустому пути (в корень нашего
приложения), вызови функцию views.home.
Шаг 2: Теперь нужно подключить эти маршруты к главному маршрутизатору проекта.
Откройте файл myproject/urls.py и измените его:
from django.contrib import admin
from django urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('blog.urls')),
]
Мы импортировали функцию include и добавили маршрут. Теперь все запросы к корневому
URL будут перенаправляться в файл blog.urls. Запустите сервер (python manage.py
runserver) и откройте http://l 27.0.0 1:8000/ — вы увидите свой текст в браузере!
Глава 3: Шаблоны (Templates) и
представление данных
Добро пожаловать в третью главу нашего руководства. До сих пор мы работали
исключительно с бэкендом: настраивали маршруты и писали простые представления,
которые возвращали обычный текст. Однако современные веб-приложения должны быть
визуально привлекательными, удобными и интерактивными. Они должны возвращать
пользователю полноценные HTML-страницы со стилями, скриптами и динамическими
данными.
Архитектура Django следует паттерну MVT (Model-View-Template), который является
вариацией классического MVC (Model-View-ControJler). В этой главе мы подробно разберем
букву "Т" — Templates (Шаблоны). Мы узнаем, как отделить логику приложения от его
внешнего вида, как передавать данные из базы данных или вычислений прямиком в HTML, и
как использовать всю мощь встроенного шаблонизатора Django (DTL).
Эта глава намеренно сделана очень объемной и подробной, так как понимание работы с
шаблонами — это фундамент для создания любого пользовательского интерфейса в Django.
Мы разберем множество примеров, подводных камней и лучших практик, чтобы вы могли
уверенно верстать самые сложные интерфейсы.
3.1. Как Django работает с HTML (встроенный шаблонизатор DTL)
Когда вы заходите на любой современный сайт, например, в интернет-магазин, вы видите
каталог товаров. Очевидно, что разработчики не создают вручную отдельный HTML-файл
для каждого из миллионов товаров. Вместо этого они создают один единственный "шаблон"
(Template) карточки товара, а данные (название, цена, картинка) подставляются в него
динамически из базы данных в момент запроса.
В Django для этого используется встроенный движок шаблонов — Django Template
Language (DTL).
Философия DTL Разделение логики и представления
Основная идея DTL заключается в гом, что в шаблонах не должно быть сложной
бизнес-лог ики. Шаблон должен от вечать только за отображение данных. Вы не можете
выполнять произвольный Python-код прямо внутри HTML файла (в отличие от, например,
PHP, где логика часто смешивается с версткой).
Почему это хорошо?
• Безопасность: Меньше шансов случайно выполнить вредоносный код или раскрыть
уязвимости.
• Чистота кода: Код легче читать и поддерживать. Дизайнеры и верстальщики могут
работать с HTML, не зная Python
• Избежание ошибок: Строгое разделение заставляет вас правильно структурировать
приложение — логика живет в views.ру, а верстка в templates.
Защита от XSS (Cross-Site Scripting)
По умолчанию DTL автоматически экранирует весь вывод. Это значит, что если
пользователь попытается ввести в поле комментария вредоносный JavaScript код
(например, <script>alert("Hacked!");</script>), Django превратит опасные символы в
безопасные HTML-сущности (<script>...). Браузер просто отобразит этот текст, но не
выполнит скрипт. Эта мощная функция защиты встроена прямо в ядро шаблонизазора.
3.2. Настройка папок для шаблонов и использование функции
render()
Прежде чем мы начнем писать HTML-код, нам нужно указать Django, где именно на
компьютере или сервере искать эти файлы. Настройка шаблонов происходит в главном
файле конфигурации вашего проекта — settings.py.
Массив TEMPLATES н settings.py
Откройте файл settings.py и найдите секцию TEMPLATES. Она выглядит примерно так:
TEMPLATES = [
{
'BACKEND': 'django.t emplate.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # Сюда мы добавим глобальную
папку
'APF_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'djangc.contrib.messages.context _proccssors.messages',
] ,
},
},
]
Разберем ключевые параметры этой настройки:
• BACKEND: Указывает, какой движок шаблонов использовать. По умолчанию это DTL.
Django также поддерживает сторонние движки, такие как Jmja2, но в 95% случаев
встроенного DTL более чем достаточно.
• DIRS; Это список директорий (папок), в которых Django будет искать шаблоны в первую
очередь. Это идеальное место для общих шаблонов, которые используются всем сайтом
(например, базовая структура HTML, общие меню, футеры). Мы добавили сюда
BASE_D1R / 'templates', что означает, что мы должны создать папку "templates" в корне
нашего проекта (там же, где лежит manage.py).
• APP_DJRS. Установлено в True. Это важнейшая настройка! Она говорит Django: "Зайди в
каждое зарегистрированное приложение (из INSTALLED_APPS) и проверь, есть ли
внутри него папка templates". Если да, то шаблоны из нее также будут доступны.
Пространства имен (Namespacing) шаблонов
Представьте, что у вас есть два приложения blog (блог) и shop (магазин). В обоих
приложениях вам нужно создать шаблон для главной страницы, и вы логично называете их
index.html. Если вы положите их просто как biog/templates/index.html и
shop/templates/index.html, произойдет конфликт. Django найдет первый попавшийся
index.html и использует его везде.
Чтобы этого избежать, в Django используется конвенция пространств имен. Внутри папки
templates приложения вы создаете еще одну папку с именем самого приложения:
• Правильно: blog/templates/blog/index.html
• Правильно: shop/templates/stiop/index.html
Теперь, когда вы будете вызывать шаблон blog/index.html, Django точно будет знать, какой
файл вам нужен.
Функция render!)
В предыдущей главе мы использовали HttpResponse для возврата простого текста. Для
возврата шаблона используется удобная функция-обертка renderQ. Она берет объект
запроса, путь к шаблону и (опционально) данные, компилирует это все вместе и возвращает
готовый HTML-код.
Пример использования в views.py:
from django.shortcuts import render
def home_view(request);
# Эта функция найдет шаблон 'blog/index.html',
# отрендерит его и вернет пользователю
return render(request, 'blog/index.html')
Функция renderQ делает за нас много рутинной работы: загружает файл с диска, создает
объект контекста, применяет шаблон и оборачивает результат в HttpResponse.
3.3. Передача контекста (context) из представления в шаблон
Статический HTML — это скучно. Настоящая сила веб-фреймворка раскрывается тогда,
когда мы начинаем передавал ь в шаблон динамические данные: информацию о
пользователе, списки статей, цены, даты и так далее.
В Django эти данные передаются с помощью словаря (dictionary) Python, который
называется контекстом (context). Ключи этого словаря станут переменными внутри вашего
HTML-шаблона, а значения — тем, что будет выведено на экран.
Пример передачи простых данных
Давайте модифицируем нашу функцию представления, чтобы передать имя пользователя,
его возраст и статус подписки:
from django.shortcuts import render
def user_profile(request):
# Допустим, мы получили эти данные из базы данных
user name = "Алексей"
use,r_ age = 28
is_premium = True
# Формируем словарь контекста
context = {
'name': user name,
'age': user_ige,
'premium status': is premium,
}
# Передаем context как третий аргумент в функцию render
return render(request, 'profile.html', context)
Теперь в шаблоне profile.html мы сможем использовать переменные 'name", "age" и
"premium_status". Обратите внимание, что в шаблоне используются именно ключи словаря,
а не имена локальных переменных Python.
Передача сложных структур данных
Контекст не ограничен простыми строками и числами. Вы можете передавать списки (lists],
словари (diets], объекты (objects] и наборы запросов к базе данных (QuerySets).
def store_view(request):
# Список словарей, имитирующий товары
products = [
{'id': 1, 'name': 'Ноутбук', 'price': 1500},
{'id': 2, 'name': 'Смартфон', 'price': 800},
{'id': 3, 'name': 'Наушники', 'price': 150},
]
store _info = {
'city': 'Москва',
'address': 'ул. Тверская, 1'
}
context = {
'items': products,
'info': store_info,
'manager': 'Елена',
}
return render(request, 'store/catalog.html', context)
В следующем разделе мы узнаем, как именно обращаться к этим сложным структурам
(например, как получить цену первого товара или адрес магазина] внутри самого
HTML-кода.
3.4. Базовый синтаксис шаблонов: переменные, теги (if, for) и
фильтры
Язык шаблонов Django (DTL] имеет свой собственный синтаксис, который
интерпретируется сервером перед тем, как отправить HTML в браузер. Существует три
основных строительных блока DTL: переменные, теги и фильтры.
Переменные; {{variable}}
Переменные выводят данные из контексга на страницу. Они всегда заключаются в двойные
фигурные скобки. Если мы используем пример из предыдущего раздела, мы можем вывести
имя менеджера так:
<Ы>Добро пожаловать в магазин 1</hl>
<р>Ваш менеджер: {{ manager }}</р>
Точечный поиск (Dot Lookup]: Если переменная является сложным объектом (словарем,
списком или объектом класса], DTL использует магию "точечного поиска" для доступа к
внутренним данным. В отличие от Python, где вы бы писали info ['city'] или items[0], в
шаблонах Django вы везде используете точку.
<!— Доступ к значению словаря —>
<р>Город магазина: {{ info.city }}</р>
<р>Адрес магазина: {{ info.address }}</р>
<!— Доступ к элементу списка по индексу -->
<р>Первый тсвар в списке: {{ items.0.name }} за ${{ items.0.price
}}</р>
Алгоритм работы точечного поиска: когда Django видит точку (например, too.bar], он
последовательно пытается сделать следующее:
• 1. Поиск по ключу словаря (foo["bar"]J
• 2. Поиск атрибута или метода (foo.bar)
• 3. Поиск по цифровому индексу (toolbar]]
Безмолвный провал (Silent Fallback]: Если переменная не найдена (например, вы
опечатались ({ managerr }}], Django не выбросит ошибку 500 и не сломает сайт. Он просто
выведет пустую строку. Это поведение сделано специально, чтобы мелкие огрехи верстки
не "роняли" весь портал, хотя иногда это затрудняет отладку.
Теги шаблонов: {% tag %}
Теги обеспечивают логику внутри шаблона. Они заключаются в фигурные скобки с
процентами. С их помощью можно создавать циклы, условные ветвления, подгружать
другие файлы и многое другое.
Ter IF (Условное ветвление): Позволяет выводить куски HTML только при выполнении
определенного условия. Поддерживает el if и else, а также операторы and, or, not, ==, !=, <, >,
in.
{% if user,is authenticated %}
<р>Дэбрс пожаловать обратно, {{ user.username }}1</p>
<a href="/logout/">BbMTM</a>
{% elif is_premiam %}
<р>3дравствуй, премиум-гость1</p>
{% else %)
<р>Пожалуйста, войдите в систему.</р>
<а href="/login/">Войти</а>
{% endif %}
Тег FOR (Циклы): Используется для итерации по спискам или наборам данных. Это самый
частый тег для вывода списков статей, товаров или комментариев.
<Ь2>Каталог товаров:</h2>
<ul>
{% for product in items %}
<li>{{ product.name }} - Цена: ${{ product.price }}</!!>
{% empty %}
<li>K сожалению, товаров пока нет. Мы скоро обновим
ассортимент!</1i>
{% endfor %}
</ul>
Особое внимание обратите на встроенный тег {% empty %}. Он срабат ывает в том случае,
если список items пуст или вообще не был передан. Это избавляет вас от необходимости
писать конструкцию (% if items %} перед циклом.
Переменная forloop: Внутри любого цикла for Django автоматически предоставляет доступ
к специальной переменной forloop, которая содержит метаинформацию о текущем
состоянии цикла:
• forloop counter; Текущая итерация цикла (начиная с 1).
• forloop counterO; Текущая итерация (начиная с 0).
• forloop. first; Возвращает True, если это первый элемент цикла.
• forloop.last: Возвращает True, если это последний элемент цикла.
{% for product in items %}
{% if forloop.first %}
<div class="featured-product">HoBi4iiKa 1 {{ product.name
}}</div>
(% else %}
<div class="regular-producr">{{ forloop.counter }}. {{
product.name }}</div>
{% endif %}
{% endfor %}
Фильтры: | (пайпы)
Фильтры изменяют переменные перед их отображением. Они применяются с помощью
символа вертикальной черты (pipe: |). Фильтры можно объединять в цепочки (chaining), и
некоторые фильтры принимают аргументы.
Рассмотрим самые полезные и часто используемые встроенные фильтры:
• default: Устанавливает значение по умолчанию, если переменная пуста или имеет
значение False. Пример: {{ нате|беГаи11:"Неизвестный пользователь" }}
• length: Возвращает длину списка или строки. Пример: У вас {{ items|length }} товаров в
корзине.
• lower и upper: Переводит строку в нижний или верхний регистр. Пример: {{
"Django"| upper }} станет "DJANGO".
• truncatewords: Обрезает текст до определенного количества слов (идеально для
анонсов статей). Пример: {{ article.content|truncatewords:30 }}
• date: Форматирует объекты даты и времени согласно заданному формату. Пример: {{
post.created_at|date:"d М Y"}} выведет "25 Янв 2024".
• safe: Как упоминалось ранее, Django экранирует HTML. Если вы передаете из базы
данных текст, который уже содержит безопасные HTML-теги (например, текст статьи с
абзацами <р>), и вы хотите, чтобы браузер их отрендерил, а не вывел как текст,
используйте фильтр safe. Пример: {{ article.body|safe }}. Используйте его с огромной
осторожностью и только для данных, которым вы доверяете!
3.5. Наследование шаблонов: DRV в действии (extends и block)
Принцип DRY (Don't Repeat Yourself— He повторяйся) — один из столпов разработки
протраммного обеспечения. В контексте HTML-верстки это означает, что вы не должны
копировать шапку (header), подвал (footer) и подключение стилей в каждый из десятков
Н1 ML-файлов вашего сайта. Если вам понадобится добавить новый пункт в меню, вам
придется изменять каждый файл, что неизбежно приведет к ошибкам.
Django элегант но решает эту проблему с помощью механизма наследования шаблонов. Этот
механизм позволяет вам создать один базовый "скелет" сайта, а затем создавать дочерние
шаблоны, которые лишь заполняют специфические "дыры" в этом скелете.
Создание базового шаблона (base.html)
Обычно базовый шаблон создается в глобальной папке templates на уровне проекта (о
которой мы говорили в настройке DIRS). Назовем его base.html В нем мы определяем теги
{% block %}. Блок — это место, которое дочерний шаблон может переопределить.
<!— templates/base.html -->
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>{% block title %)Мой супер сайт{% endblock %}</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<header>
<nav>
<ul>
<lixa href- "/">Главная</аХ/И>
<li><a href="/about/">0 нас</а></И>
</ul>
</nav>
</header>
<main class="container">
<!— Вот здесь будет подставляться контент других страниц —>
{% block content %}
{% enablock %}
</main>
<footer>
<p>© 2024 Все права защищены.</p>
</footer>
</body>
</html>
Обратите внимание на блоки {% block title %} и {% block content %}. В блоке title мы
задали значение по умолчанию (''Мой супер сайт"). Если дочерний шаблон не
переопределит этот блок, будет использовано это значение.
Создание дочернего шаблона (extends)
Теперь создадим шаблон для конкретной страницы, например, для вывода списка статей в
нашем блоге. Чтобы унаследовать базовый шаблон, используется тег {% extends %}. Он
обязан быть самой первой строкой в дочернем файле (даже перед комментариями).
<!— blog/teir.plates/blog/post_list.htril -->
{% extends 'base.html' %)
{% block title %}Блог - Последние записи{% endblock %}
{% block content %}
<hl>Ham корпоративный блогС/h]>
<р>3десь мы делимся новостями и статьями.</р>
<div class="articles">
{% for post in posts %}
<article>
<h2>{( post.title }}</h2>
<p>{{ post.excerpt }}</p>
<a href="#">Читать далее...</a>
</article>
{% endfor %}
</di v>
{% erdblock %}
Как это работает: Когда Django рендерит post_list.html, он сначала видит тег extends. Он
загружает base.html. Затем он ищет все теги block в post_list.html и заменяет
соответствующие пустые блоки в base.html на этот контент. В результате пользователь
получает потноценную HTML-страницу со всеми шапками и подвалами, по код остается
чистым и не дублируется.
Специфика {{ block.super}} и тег include
Иногда вам нужно не просто перезаписать блок из родительского шаблона, а добавить к
нему что-то. Например, вы хотите добавить специфичный CSS-файл только для одной
страницы, не удаляя глобальные стили.
Для этого используется переменная {{ block.super }}, которая вызывает содержимое
родительского блока.
{% block extra css %}
<!— Оставляем все стили из базового шаблона —>
{{ block.super )}
<!— Добавляем стиль только для текущей страницы —>
<link rel="stylesheet" href="/specific-page-style.css">
{% er.dblock %}
Ter include: Кроме наследования, есть еще механизм включения компонентов (вставок).
Если у вас есть сложный кусок HTML, который повторяется на разных, не связанных
страницах (например, карт очка товара или виджет подписки на рассылку), вы можете
вынести его в отдельный файл (например, _product_card.html) и вставлять с помощью тега
include:
<div class="sidebar">
<бЗ>Подпишитесь на нас</ЬЗ>
{% include 'components/newsletter_widget.html' %}
</div>
3.6. Подключение статических файлов (CSS, JavaScript, изображения)
Динамический HTML готов, структура выстроена. Но наш сайт пока выглядит как привет из
90-х, потому что мы не подключили стили (CSS), скрипты (JavaScript) и картинки. В
терминологии веб-разработки такие файлы называются "статическими' (Static Files),
потому что они не генерируются сервером динамически на каждый запрос, а отдаются ' как
есть".
В Django работа со статикой требует небольшой настройки, так как фреймворк различает
подходы к статике во время разработки (на локальном компьютере) и в продакшене (на
боевом сервере). Сейчас мы рассмотрим настройку для режима разработки.
Настройка settings.py для статики
Убедитесь, что в конце вашего файла settings.py есть следуюшие строки:
# URL-адрес, по которому будут доступны статические файлы в браузере
STATIC_URL = '/static/'
# Список папок, где Django будет искать статические файлы
import os
from pathlib import Path
BASE_DIR = Path(___file___).resolve().parent.parent
STATICFILES DIRS = [
BASE DIR I "static", # Глобальная папка для статики в корне
проекта
]
Создайте папку static в корне проекта. Внутри нее для порядка принято создавать подпапки
css, js и images. Положите свой файл стилей (например, style.css) в папку static/css/
Тег {% load static %}
Теперь мы хотим подключить этот CSS-файл в наш base.html. Никогда не прописывайте
пути к статике жестко (хардкодом), например href="/static/css/style.css". Если вы позже
решите разместить статику на отдельном домене (CDN), вам придется переписывать все
шаблоны.
Правильный путь — использовать специальный тег {% static %) Чтобы он стал доступен,
его нужно загрузить в самом верху шаблона с помощью тега {% load static %}.
< !— base.html -->
{% load static %} <!— Обязательно загружаем библиотеку статики! —>
<!DOCTYPE html>
<html lanq="ru">
<head>
<meta charset="UTF-8">
<title>{% block title %}Мой сайт{% endblock %}</title>
<!— Правильное подключение CSS -->
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
cimg src=”{% static 'images/logo.png' %}" alt-"Логотип сайта">
<!— Контент —>
{% block content %}{% endblock %}
<!— Правильное подключение JS перед закрывающим body —>
<script src="{% static 'js/main.js' %}"></script>
</body>
</html>
Когда сервер рендерит этот шаблон, тег {% static 'css/style.css’ %} автоматически
преобразуется в правильный URL-адрес на основе настроек STATICJJRL (обычно это
превратится в /static/css/style.css].
Итоги 3 главы
Вы проделали oi ромную работу! В этой главе мы детально разобрали, как Django
генерирует пользовательские интерфейсы. Вы узнали, как настраивать директории для
шаблонов, как передавать сложные структуры данных из базы прямо в HTML с помощью
функции render. Мы изучили язык шаблонов DTL: переменные, мощные циклы for,
условные конструкции if и модификаторы данных (фильтры].
Самое главное: вы освоили принцип DRY с помощью механизма наследования (extends и
block], который позволит вам масштабировать ваши проекты до сотен страниц, сохраняя код
чистым и легко поддерживаемым. И, наконец, мы "оживили" наши страницы, научившись
правильно подключать статические ресурсы (CSS и JSJ.
Теперь, когда мы умеем выводить данные на экран в красивом виде, пришло время
разобраться, откуда эти данные берутся. В следующей, 4-й главе, мы погрузимся в сердце
любого веб-приложения — базы данных и систему ORM Django.
Глава 4: пазы данных и Модели
(Models)
До сих пор мы работали с так называемыми "статичными" данными или передавали в
шаблоны жестко закодированные словари (hardcoded data). Но настоящие веб-приложения
так не работают. Блог должен хранить тысячи статей, интернет-магазин — списки товаров и
заказов, а социальная сеть — профили пользоват елей и их сообщения. Для надежного,
быстрого и структурированного хранения этой информации используются базы данных
(БД).
В этой главе мы погрузимся в одну из самых мощных частей фреймворка Django — его
подсистему работы с базами данных. Вы узнаете, почему вам (па первых порах) даже не
придется писать SQL-запросы вручную, как описыват ь структуру таблиц с помощью
обычных классов Python, и как связывать эти таблицы между собой. Мы также
познакомимся с механизмом миграций, который делает эволюцию вашей базы данных
безболезненной и безопасной.
4.1. Введение в ORM (Object-Relational Mapping)
Традиционно для работы с реляционными базами данных (такими как PostgreSQL, MySQL
или SQLite) разработчики используют язык структурированных запросов — SQL
(Structured Query Language). Если бы вы писали приложение без фреймворка, получение
всех опубликованных статей из базы данных выглядело бы примерно так:
SELECT * FROM bloq_post WHERE status = 'published' ORDER BY created_at
DESC;
Однако смешивание SQL-кода и кода на Python часто приводит к тому, что приложение
становится трудно поддерживать. Кроме того, разные базы данных используют слегка
отличаюшиеся диалекты SQL. Если вы решите перенести проект с SQLite на PostgreSQL,
вам придется переписывать часть SQL-запросов.
Django решает эту проблему с помощью технологии ORM (Ohject-Relational Mapping —
объекто-реляционное отображение).
Что такое ORM?
ORM — это слой абстракции между вашим Python-кодом и базой данных. Он позволяет вам
взаимодействовать с базой данных, используя парадигму объектно-ориентированного
программирования. Вместо таблиц в БД вы работаете с классами Python (Моделями), а
вместо строк в таблицах — с экземплярами (объектами) этих классов.
Тот же самый SQL-запрос, который мы привели выше, с использованием Django ORM будет
выглядеть так:
Post.objects.filter(status-'puolished') .oraer_by('-created_ at')
Преимущества использования Django ORM:
• Абстракция от СУБД: Вы пишете код на Python, a Django сам переводит его в
правильный SQL-запрос для той базы данных, которая сейчас подключена. Смена БД
происходит путем изменения одной настройки.
• Безопасность: ORM автоматически экранирует параметры запросов, что надежно
защищает ваше приложение от одной из самых опасных уязвимостей — SQL-инъекций
(SQL Injection}.
• Скорость разработки: Вы работаете с привычными объектами Python, получаете
автодополнение в редакторах кода (IDE] и меньше отвлекаетесь на синтаксис базы
данных.
• Богатый API: ORM предоставляет огромный набор методов для фильтрации, агрегации
(сумма, среднее значение] и создания сложных выборок данных.
4.2. Настройка базы данных (использование SQLite по умолчанию)
Django поддерживает несколько мощных реляционных баз данных из коробки: PostgreSQL,
MariaDB, MySQL, Oracle и SQLite. Настройки базы данных находятся в файле settings ру в
словаре DATABASES.
Словарь DATABASES
Если вы откроете файл myproject/settings.py, который был сгенерирован командой
startproject, вы увидите следующую конфигурацию по умолчанию:
DATABASES = {
'default': {
'ENGINE': 'dj ango.db.oackends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
Что означают эти параметры:
• ENGINE: Указывает, какой драйвер базы данных использовать. Для SQLite это
'django db_backends.sqlite3'.
• NAME: Имя базы данных. В случае с SQLite, база данных — это просто файл на вашем
компьютере. BASE_DIR / 'db.sqhte3’ означает, что файл db.sqlite3 будет создан прямо в
корне вашего проекта.
Почему SQLite?
SQLite — это встроенная легковесная бага данных, которая не требует установки отдельного
сервера баз данных. Она идеально подходит для разработки, обучения и небольших
проектов. Для запуска нашего приложения прямо сейчас вам не нужно ничего скачивать или
настраивать дополнительно.
Важно: SQLite не предназначена для высокона] руженных проектов (Production] с большим
количеством одновременных записей. Когда мы дойдем до Главы 9 (Развертывание), мы
заменим SQLite на надежную промышленную базу данных — PostgreSQL. Благодаря ORM,
нам не придется переписывать ни строчки логики нашего приложения!
4.3. Создание моделей: классы и типы полей
Модель в Django — это единственный, определяющий источник информации о ваших
данных. Она содержит основные поля и поведение хранимых данных. Каждая модель
обычно отображается на одну таблицу в базе данных.
Модели определяются в файле models.py вашего приложения. Давайте создадим модель для
статьи в блоге (Post) в нашем приложении blog.
Написание класса модели
Откройте файл blog/models.py и напишите следующий код:
from django.Ob import models
class Post(models.Model):
# Поля нашей модели (будущие колонки в таблице БД)
title = models.CharField(max_length=200, verbose пате="3аголовок")
content = models.TextField(veroose_name="TeKCT статьи")
is_published = models.BooleanField(default=True,
уегЬозе_пате="0публиковано")
created at = models.DateTimeField(auto now idd=True,
verbose^пате="Дата создания")
updared_at = models.DateTimeField(auto_now=True,
чегЬозе_пате="Дата обновления")
class Meta:
verbose name = "Статья"
verbose_name_plural
"Статьи"
ordering
['-created at']
def str (self) :
return self.title
Разбор типов полей (Field Types)
Каждый атрибут класса (title, content...) представляет собой поле базы данных. Django
предоставляет множество типов полей для разных видов данных:
• CharField: Используется для коротких строк (например, заголовков, имен). Обязательно
требует указания параметра maxjength (максимальная длина). В базе данных
превращается в VARCHAR.
• TextField: Используется для длинных текстов без ограничения по длине (например, тело
статьи, комментарий). В базе данных это тип TEXT.
• BooleanField: Логическое поле (True или False).
• JntegerField / FloatField / DecimalField: Поля для хранения целых чисел, чисел с
плавающей точкой и чисел с фиксированной точностью (идеально для денег].
• DatcTimeField / DateField: Поля для хранения даты и времени. У них есть два очень
полезных аргумента:
• — auto_now_add-True: Автоматически устанавливает текущее время при создании
нового объекта (отлично подходит для поля ‘'Дата создания").
• — auto_now=True: Автоматически обновляет поле на текущее время при каждом
сохранении объекта (для поля "Дата обновления").
Параметры полей (Field Options)
Вы можете настраивать поведение полей с помощью аргументов:
• null=True: Позволяет хранить пустое значение в базе данных (NULL).
• blank=True: Позволяет оставлять это поле пустым в HTML-формах при заполнении.
• default: Задает значение по умолчанию, если оно не было передано явно.
• choices: Позволяет ограничить выбор значения из заранее определенного списка
(полезно для статусов, например: "Черновик", "Опубликовано").
• verbose_name: Человекочитаемое имя поля, коюрое будет использоваться в формах и
панели администратора Django.
Метаданные класса и магический метод______str__
Внутри модели можно определить внутренний класс Meta. В нем задаются настройки самой
модели, а не отдельных полей. Например, verbose_name позволяет задать красивое имя
модели в единственном и множественном числе для админки. A ordering - ['-created_at']
говорит Django всегда сортировать статьи по убыванию даты создания (знак минус означает
обратный порядок).
Метод _str_(self) — это строковое представление объекта. Без него, если вы распечатаете
статью, вы увидите что-то вроде <Post object (1)> С этим методом вы увидите красивое
название статьи, что критически важно для удобной работы в админ-панели и консоли.
4.4. Связи между таблицами
Мощь реляционных баз данных заключается в том, что таблицы могут быть связаны друг с
другом. В Django ORM есть три типа святей, которые реализуются с помощью специальных
полей.
Один-ко-Многим (One-tu-Many); ForeignKey
Это самый частый тип связи. Например, у одной категории может быть много статей, но
каждая статья принадлежит только одной категории. Или один автор может написать много
статей. Для реализации этой связи используется ForeignKey.
class Category(models.Model):
name = models.CharField(max_length=lCO, db_index=True)
class Post(models.Model):
# ... другие поля ...
# Связываем статью с категорией
category = models.ForeignKey(Category, on delete=models.CASCADE,
related_name='posts')
Важный аргумент on.delete. Он определяет, что должно произойти со статьями, если мы
удалим категорию models.CASCADE означает "каскадное удаление": если удалить
категорию, все статьи внутри нее также будут удалены. Другие варианты: models.SET_NULL
(сделать поле категории пустым], models.PROTECT (запретить удаление категории, если в
ней есть ст атьи].
Аргумент related .name позволяет получить доступ к статьям со стороны категории.
Например: my_category.posts.all().
Многие-ко-Многим (Many-to-Many): ManyToManyField
Используется, когда объект на одной стороне связи может быть связан с несколькими
объектами на другой стороне, и наоборот. Классический пример — система тегов. У одной
статьи может быть много тегов ("Python", "Django", "Web' ], а один тег может быть
прикреплен к множеству разных статей.
class Tag(models.Model):
name = models.CharField(max_length=50)
class Post(models.Model):
# ... другие поля ...
tags = models.ManyToManyField(Tag, blank=True)
Django автоматически создаст промежуточную таблицу (join table] в базе данных для
управления этой связью.
Один-к-Одной (One-to-One). OneToOneField
Концептуально похоже на ForeignKey, но с уникальным отраничением. Один объект может
быть связан только с одним другим объектом. Часто используется для расширения
встроенной модели пользователя Django (добавление аватарок, биографии, ссылок на
соцсети].
from django.contrib.auth.models import User
class User Profile(models.Model) :
user = models.OneToOneField(User, or_delete-moaels.CASCADE)
bio = models.TextField(blank=True)
avatar = models.ImageFleld(upload to='avatars/')
4.5. Миграции: подготовка (makemigrations) и применение (migrate)
Мы написали код моделей, но база данных пока пуста и ничего о них не знает. Чтобы
создать реальные таблицы в файле db.sqlite.3 на основе наших классов Python, используется
система миграций Django.
Миграции — это своеобразная система контроля версий для вашей базы данных. Django
отслеживает изменения в моделях (добавление поля, удаление модели, изменение типа
данных) и создает специальные файлы (миграции), в которых записаны инструкции по
изменению БД.
Шаг 1: Подготовка миграций (makemigrations)
Каждый раз, коща вы вносите изменения в файл models.ру, вам нужно зафиксироват ь эти
изменения. Откройте терминал и выполните команду:
python manage.py makemigrations
Вы увидите вывод, похожий на этот:
Migrations for 'blog':
blog/migrations/0001 initial.ру
- Create model Category
- Create model Tag
- Create model Post
Django проанализировал ваши модели и создал файл OOOIJnitial.py в папке migrations
вашего приложения. Этот файл содержит Python-код, описывающий создание таблиц. База
данных все еше не изменена!
Шаг 2: Применение миграций (migrate)
Чтобы применить подготовленные инструкции к самой базе данных (т.е. выполнить
SQL-запросы на создание таблиц CREATE TABLE), выполните команду:
python manage.py migrate
Эта команда просматривает все невыполненные миграции во всех приложениях (включая
встроенные приложения Django, такие как auth для пользователей и admin) и применяет их.
Теперь таблицы физически существуют в базе данных db.sqlite3, и вы можете сохранять в
них данные.
4.6. Интерактивная оболочка Django shell и базовые запросы к БД
(CRUD)
Чтобы проверить, как работает ORM, нам не нужно сразу писать представления (views) и
HTML-формы. Django предоставляет мощную интерактивную консоль — Django shell. Это
обычный Python shell, но в него уже загружены все настройки вашего проекта и
подключена база данных.
Запустите консоль командой:
python manage.py shell
CRUD операции (Create, Read, Update, Delete)
В консоли мы можем взаимодейст вовать с нашими моделями. Давайте импортируем модель
Post: "from biog.models import Post".
1. Создание (Create):
# Способ 1: Создать объект и вызвать метод save()
postl = Post(title='Моя первая статья', content='Привет, мир!')
postl.saveO # Только в этот момент происходит запись в БД (INSERT)
# Способ 2: Использование метода create() менеджера objects
Post.objects.create(title='Изучаем CRM', content='Django CRM очень
удобен.')
2. Чтение (Read):
У каждой модели есть менеджер "objects", через который выполняются все запросы к БД.
# Получить все записи (возвращает QuerySet - список объектов)
all_posts = Post.objects.all()
# Получить одну конкретную запись по ID (или вызовет ошибку
DoesNotExist)
post = Post.objects.get(id=l)
print(pest.title) # Выведет: Моя первая статья
# Фильтрация данных (WHERE в SQL)
published posts = Post.objects.filter(is_published=True)
python_posts = Post.objects.filter(title_ icontains='orm') # Поиск no
подстроке без учета регистра
# Исключение данных
drafts = Post.objects.exclude(is_published=True)
3. Обновление (Update):
# Получаем объект, меняем атрибут и сохраняем
post = Post.objects.get(id-1)
post.title = 'Обновленный заголовок'
post.save() # Выполняет SQL запрос UPDATE
4. Удаление (Delete):
post = Post.objects.get(id=2)
post.delete() tf Выполняет SQL запрос DELETE
Чтобы выйти из консоли, введите команду exit() или нажмите Ctrl+Z (Windows) / Ctrl+D
(Мас/Linux).
4.7. Встроенная панель администратора: регистрация моделей
Одна из главных "киллер-фич" Django, за которую его так любят — это встроенная панель
администратора (Django Admin). Она автоматически генерирует профессиональный
веб-интерфейс (CRUD-интерфейс) для управления контентом вашего сайта на основе ваших
моделей. Вам не нужно тратить недели на написание админки с нуля!
Создание суперпользователя
Чтобы зайти в админку, нам нужен аккаунт администратора. Выйдите из shell и выполните в
т ерминале:
python manage.py createsuperuser
Введите имя пользователя (например, admin), email (можно оставить пустым) и надежный
пароль (при вводе пароля символы не отображаются в терминале — это нормально).
Регистрация моделей в admin.ру
Запустите сервер (python manage.py runserver) и перейдите по адресу
http://1270.0.1:8000/admin/. Вы сможете войти, но не увидите своих статей. Чтобы
модель появилась в админке, ее нужно зарегистрировать.
Откройте файл blog/admin.py и добавьте код:
from djапдс.contrib import admin
from .models import Post, Category, Tag
# Простая регистрация
admin.site.register(Category)
admi n.site.register(Tag)
Кастомизация админки (ModelAdmin)
Прос1 ая регистрация работ ает, но мы можем сделать интерфейс намного удобнее, создав
класс ModelAdmin. Добавим его для модели Post:
0admin.register(Post)
class PostAdmin(admin.ModelAdmin):
# Поля, которые будут отображаться в виде колонок в списке статей
list_display = ('title', 'category', 'created_ao', 'is_published')
# Добавляем панель фильтрации сбоку
list_filter = ('is_published', 'category', 'created at')
# Добавляем строку поиска (поиск будет идти по заголовку и тексту)
search_fields = ('title', 'content')
# Какие поля являются ссылками на редактирование
list iisplay_links = ('title',)
# Автоматическое заполнение полей (например, slug на основе title
- если бы оно у нас было)
# prepopulated fields = {'slug': ('title',)}
Теперь обновите страницу в браузере. Вы увидите полноценный интерфейс: красивую
таблицу со статьями, фильтры справа, работающую строку поиска и календарь для выбора
даты при редактировании статьи. И все это мы получили, написав всею несколько строк
кода конфигурации!
Итоги 4 главы
В этой главе мы заложили прочный фундамент работы с данными в Django. Вы изучили
концепцию ORM, которая позволяет общаться с базой данных на языке Python. Мы создали
свои первые модели, настроили типы полей и связали таблицы между собой отношениями
"Один ко многим" и "Многие ко многим".
Вы освоили критически важный процесс миграций (makemigrations и migrate), без
которого невозможно развитие архитектуры проекта. Также вы научились делать базовые
CRUD- запросы через консоль Django shell и, что самое впечатляющее, настроили
мощнейшую панель администратора под свои нужды.
Теперь в нашей базе данных есть контент. В следующей главе мы научимся выводить этот
контент пользователю, создавая формы для добавления данных прямо с сайга, не заходя в
панель администратора!
Глава 5: Работа с формами (Forms)
До сих пор наше приложение работало в одностороннем порядке: сервер извлекал данные
из базы и показывал их пользователю. Пользователь мог лишь переходить по ссылкам и
читать информацию. Но полноценный веб-сайт требует интерактивности. Пользователи
должны иметь возможность оставлять комментарии, регистрироваться, загружать аватарки,
оформлять заказы и отправлять сообщения в службу поддержки.
Основой любого взаимодействия пользователя с сервером в веб-среде являются
HTML формы. В этой главе мы детально разберем, как Django превращает рутинную и часто
небезопасную работу с HTML-формами в элегантный, безопасный и строго типизированный
процесс. Мы пройдем путь от создания простых форм обратной связи до мощных форм,
привязанных напрямую к нашим моделям баз данных.
5.1. HTML-формы: сложности и подводные камни
Прежде чем изучать инструменты Django, давайте вспомним, как работают обычные
HTML-формы и почему разработчики так не любят обрабатывать их "вручную".
Анатомия HTML формы
Стандарт ная HTML-форма состоит из тега <form>, внутри которого располагаются
различные поля ввода (<input>, <textarea>, <select>) и кнопка отправки (<button
type="submit">). У формы есть два злавных атрибута:
• action: URL-адрес, на который будут отправлены данные после нажатия кнопки. Если
оставить пустым, данные отправятся на текущий URL.
• method: HTTP-метод, который будет использован для отправки. Чаще всего это GET или
POST.
GFT против POST
Разница между GET и POST фундаментальна для веб-разработки:
• Метод GET: Данные формы прикрепляются прямо к URL-адресу после знака вопроса
(например, /search/?q=django&sort=new). Этот метод идемпотентен — он не должен
изменять состояние сервера или базы данных. GET идеально подходит для форм поиска
или фильтрации, так как результатами можно легко поделиться, скопировав ссылку.
• Метод POST: Данные передаются скрыто в теле HTTP-запроса (request body). Этот
метод используется, когда действие пользователя приводит к изменениям: созданию
новой записи, обновлению профиля, списанию средств или загрузке файла. Данные
POST-запроса нельзя добавить в закладки браузера, и браузеры часто предупреждают
пользователя, если он пытается обновить страницу, отправленную методом POST
("Подтвердите повторную от правку формы").
Проблемы ручной обработки
Если бы мы обрабатывали форму без помощи фреймворка, нам пришлось бы столкнуться с
массой проблем:
1. Извлечение данных: В Python пришлось бы вручную доставать каждое поле из сырого
запроса (request.POST.getj "username"]].
2. Приведение типов: HTML-формы всегда отправляют данные в виде текста [строк]. Если
вам нужен возраст пользователя, вам придется писать логику конвертации строки "25" в
целое число 25, обрабатывая при этом возможные ошибки [ValucError], если пользователь
ввел "двадцать пять".
3. Валидация: Вы должны проверить, не пустое ли обязательное поле, правильный ли
формат у email-адреса, не слишком ли длинный пароль, и уникально ли имя пользователя в
базе данных.
4. Повторный рендеринг с ошибками: Если пользователь ошибся в одном из 10 полей, вы
должны вернуть ему форму, показав сообщение об ошибке, но при этом сохранив данные,
которые он ввел в остальные 9 полей [чтобы ему не пришлось заполнять все заново). В
чистом HTML это превращается в настоящий кошмар из условий и подстановок значений в
атрибут "value".
Django Forms берет все эти проблемы на себя.
5.2. Использование классов Form в Django
В Django формы описываются точно так же, как и модели: с помощью классов Python.
Фреймворк имеет библиотеку django.forms, которая предоставляет классы полей, виджеты
и механизмы валидации.
Создание файла forms.py
Хорошей практикой является создание отдельною файла forms ру внутри папки вашего
приложения [например, blog/forms.py). Давайте создадим простую форму обратной связи
(Contact Form).
from django import forms
class ContactForm(forms.Form):
# Поля нашей формы
name = forms.CharField(max_lenqth=100, label='Bauie имя')
email = forms.EmailField(label='Email адрес')
subject = forms.CharField(max length=200, 1аЬе1='Тема сообщения')
message = forms.CharField(widget=forms.Textarea, label='Текст
сообщения')
send copy = forms.BooleanField(required=False, label='Отправить
копию мне')
Разбор полей формы
Обратите внимание, наеколько это похоже на создание моделей. Однако здесь мы
наследуемся от forms.Form, а не от models.Model.
• CharField: Базовое текстовое поле. По умолчанию рендерится как <mpui cype="text">.
• EmailField: Специальное ноле, которое автоматически проверяет, является ли
введенный текст корректным email-адресом (наличие @ и домена).
• BooleanField: Рендерится как чекбокс (<input type="checkbox">). Обратите внимание
на requii ed=False. По умолчанию все поля в Django-формах обязательны для
заполнения. Если снять галочку с чекбокса, он просто не отправится на сервер, что
вызовет ошибку валидации. Поэтому чекбоксы почти всегда помечают как
необязательные.
Параметр label позволяет задать человекочитаемое название, которое будет выведено рядом
с полем (в теге <label>).
Виджеты (Widgets)
Виджет — это класс Django, который отвечает за то, как именно поле будет преобразовано в
HTML-код. В нашем примере поле message является CharField, которое по умолчанию
создает однострочный <inpnt>. Но для сообщения нам нужно многострочное поле. Поэтому
мы явно переопределяем виджет: widget=forms.Textarea. Теперь Django огрендерит его как
тег <textarea>.
Виджеты также позволяют добавлять CSS-классы, плейсхолдеры и другие НТМБ-азрибуты
прямо из Python-кода:
name = forms.CnarFieid(
max_length=10C,
widget=fcrms.Textinput(attrs={
'class': 'form-control',
'placeholder': 'Иван Иванов'
})
)
5.3. Отрисовка форм в шаблоне и защита от CSRF
Теперь, когда у нас есть класс формы, нам нужно передать его экземпляр в шаблон и
отрисовать.
Передача формы из представления
Откройте views.py и создайте функцию, которая будет просто возвращать пустую форму
пользователю.
from django.shortcuts import render
from .forms import ContactForm
def contact_view(request) :
# Создаем пустой (несвязанный) экземпляр формы
form = ContactForm()
# Передаем форму в контекст
context = {'form': form.}
return render(request, 'blog/contact.html', context)
Важный термин: Несвязанная форма (Unbound Form) — это форма, в которую не передали
никаких данных. Она используется исключительно для оз рисовки пустых полей HTML.
Рендеринг в шаблоне {(untact.html)
В шаблоне мы можем вывести всю форму целиком всего одной строчкой! Django
предоставляет несколько встроенных методов отрисовки (as.p, as_table, as. ul).
{% extends "base.html" %}
{% block content %}
<Ь2>Свяжитесь с нами</Ь2>
<form method="post" action="">
(% csrf_token %}
<1— Вывод формы: каждое поле будет обернуто в тег <р> —>
{{ form.as_p }}
<button type="submit">OTnpaBi4Tb coo6ineHMe</button>
</form>
{% endblock %}
Что такое {% csrf_token %} и зачем он нужен?
Если вы попытаетесь отправить POST-форму в Django без тега {% csrf_token %), вы
гарант ированно получите страшную ошибку 40.3 Forbidden. Это встроенная защита.
CSRF (Cross-Site Request Forgery] — это межсайтовая подделка запроса. Злоумышленник
может создать на своем вредоносном сайте форму, которая будет отправлять скрытый
POST-запрос на ваш сайт (например, запрос на удаление аккаунта или перевод денет). Если
пользователь в этот момент авторизован на вашем сайте, браузер автоматически прикрепит
его файлы cookie (сессию) к вредоносного запросу, и ваш сервер выполнит действие, думая,
что этого захотел сам пользователь.
Как Django с этим борется:
Тег {% csrf_tokcn %} генерирует уникальный, криптографически стойкий скрытый токен
(длинную строку) внутри формы (<input type="hidden" namc="csrfmiddlcwarctokcn"
value="...’ >). При получении POST-запроса Django проверяет, совпадает ли токен из формы
с токеном, хранящимся в cookie пользователя. Поскольку вредоносный сайт не может
прочитать этот токен (из-за полизики Same-Origin Policy браузеров), азака становится
невозможной.
Правило: Вы обязаны добавлять {% csrf_token %) внутрь КАЖДОЙ формы, использующей
метод POST.
5.4. Обработка POST-запросов и валидация данных
Пока наша форма только отображается. Если нажать кнопку "Отправить", запрос уйдет на
тот же URL, но ничего не произойдет. Нам нужно научить паше представление [view]
различать GET-запросы (показ формы) и POST-запросы (обработка отправленных данных).
Паттерн обработки формы
Стандарт ный подход в Django выглядит гак:
from django.shortcuts import render, redirect
from django.http import HttpResponse
from .forms import ContactForm
def contact_view(request):
if request.method == 'POST':
# 1. Создаем связанную (Bound) форму с данными из запроса
form = ContactForm(request.POST)
# 2. Проверяем данные на валидность
if form.is_vaiid():
# 3. Данные верны! Извлекаем очищенные данные
name = form.cleaned_data}'name']
email = form.cleaned_data['email']
message = form.cleaned_data['message' ]
# (Здесь логика отправки email, сохранения в БД и т.д.)
print(f"Сообщение от {name}: {message}")
# 4. ОБЯЗАТЕЛЬНО делаем перенаправление (Redirect)
return HttpResponse("Спасибо! Ваше сообщение отправлено.")
else:
# Если это GET-запрос, просто показываем пустую форму
form = ContactForm()
# 5. Если форма невалидна, мы дойдем сюда.
# Форма будет содержать введенные данные и сообщения об ошибках!
return render(request, 'blog/contact.html', {'form': form})
Валидация (is_valid() и cleaneddata)
Когда мы вызываем метод form.is_valid(), под капотом происходит магия. Django прогоняет
данные через все проверки:
• Проверяет, заполнены ли все обязательные поля.
• Проверяет типы (чго email действительно email, а не просто строка).
* Проверяет длину (maxjength).
Если хотя бы одно условие не выполнено, is_va)id() вернет False, а в объект формы будут
добавлены сообщения об ошибках. При повторном рендеринге в шаблоне ({{ form.as р }})
эти ошибки автоматически появятся рядом с проблемными полями красным текстом, а сами
поля сохранят введенный пользователем текст.
Если форма валидна, Django создает словарь cleaned.data. Важно: вы ВСЕГДА должны
брать данные только из cleaned, data, а не напрямую из request.POST. В cleaned data
данные уже приведены к правильным Python-типам (например, строка "25" станет числом
25, а дата строкой — объектом datetime).
Кастомная валидация
Иногда встроенных проверок недостаточно. Например, вы хотите запретить регистрацию
пользователей с email-адресами определенного домена (скажем. @spam.com). Вы можете
добавить свои методы очистки прямо в класс формы.
class ContactForm(forms.Form):
# ... поля ...
# Метод должен называться с1еап__<имя_поля>
def cleari_email (self) :
# Получаем данные из текущего cleaned data
email = self.cleaned_data.get('email')
if email.en.dswith (' @spam.com') :
# Выбрасываем специальное исключение ValidationError
raise forms-ValidationError("Мы не принимаем письма с
этого домена.")
# Обязательно возвращаем очищенное значение
return email
Если ошибка затрагивает сразу несколько полей (например, "Пароль" и "Подтверждение
пароля ' должны совпадать), нужно переопределить метод clean(), который отвечает за всю
форму целиком.
Паттерн Post/Redirect/Get
В коде выше, после успешной обработки формы, мы не просто рендерим шаблон (render), а
возвращаем HttpResponse (в реальном проекте мы бы использовали redirect() на
специальную страницу "Спасибо"). Зачем?
Это архитектурный паттерн PRG (Post/Redirect/Get). Если после успешного POST-запроса
вы вернете обычную HTML-страницу через render, то, если пользователь нажмет кнопку F5
(Обновить) в браузере, браузер заново отправит POST-запрос, что приведет к дублированию
данных (например, двойному списанию денег или созданию двух комментариев). Redirect
заставляет браузер сделать новый чистый GET-запрос по новому адресу, исключая эту
проблему.
5.5. Формы на основе моделей (ModelForm): быстрое сохранение
данных в БД
Чаще всего формы в веб-приложениях используются для того, чтобы создавать новые
записи в базе данных. Вы создали модель Post. Теперь вам нужна форма, чтобы
пользователи могли добавлять новые статьи.
Если мы будем использовать обычный forms.Form, нам придется дублировать весь код:
заново прописывать поля title, content, следить за совпадением типов и maxjength, а во
views.py вручную брать данные из cleaned.data и передавать их в Post objects.createQ. Это
нарушение принципа DRY
Магия ModelForm
Django решает эту задачу гениально просто с помощью класса ModelForm. Он позволяет
автоматически сгенерировать форму на основе существующей модели!
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
# Указываем, к какой модели привязана форма
model = Post
# Указываем, какие поля модели должны присутствовать в форме
fields - ['title', 'content', 'category', 'tags']
# Или можно использовать ' all ', чтобы включить все поля
# Но в целях безопасности лучше перечислять явно!
Это всё! Django сам посмотрит в модель Post, увидит, что title — это
CharField(max_length=200), и создаст текстовый input с соответствующей валидацией. Он
увидит, что category — это ForeignKey, и создаст выпадающий список (<select>) со всеми
доступными категориями из базы данных!
Сохранение данных: метод save()
Использование ModelForm невероятно упрощает код представления (view).
from django.shortcuts import render, redirect
from .forms import PostForm
def create_pcst(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
# Самое главное отличие!
# Метод save() автоматически создаст и сохранит объект в
БД!
new_post = form.save()
return redirect('blog-home') # Перенаправляем на главную
el se:
form - PostForm()
return render(request, 'blog/post form.html', {'form': form})
Вызов form.save() делает всю черновую работу: он берет очищенные данные, инстанцирует
объект модели Post и вызывает у него метод сохранения базы данных.
Редактирование существующих объектов
ModelForm идеален не только для создания, но и для редактирования (Update) данных.
Если вы передадите в форму существующий объект модели через аргумент instance, форма
автоматически заполнится его данными. А при вызове saveQ Django обновит запись, а не
создаст новую.
def edlt_post(request, post id):
# Получаем статью из БД
post = Post.objects.get(id=post_id)
if request.method == 'POST':
# Передаем и данные запроса (POST), и существующий объект
form = PostForm(request.POST, instance=posr)
if form.is_valid():
form.save() # Запись обновится!
return redirect('blog-home')
el se:
# При GET-запросе форма предзаполнится текстом статьи
form = PostForm(instance=post)
return render(request, 'blog/post form.html', {'form': form})
Метод save(commit=False)
Что делать, если в модели есть обязательное поле (например, "автор"), но мы не хотим,
чтобы пользователь сам выбирал его в форме? Мы хотим, чтобы автором автоматически
становился текущий залогиненный пользователь.
В этом случае нам нужно приостановить сохранение ModelForm, чтобы добавить данные
вручную. Для этого используется аргумент commit=False.
if form.is valid():
# Создаем объект модели в памяти, но пока не сохраняем в БД!
post_obj = form.save(commit=FaIse)
# Модифицируем объект вручную (добавляем автора)
post_obj.author = request.user
# Теперь окончательно сохраняем в базу данных
post_obj.save()
# Важно: если в форме есть поля МапуТоМапу (как теги),
# их нужно сохранить отдельно с помощью savt_m2m()
form.save_m2m()
Итоги 5 главы
В этой огромной и крайне важной главе мы разобрали механизм, который делает
веб-приложения живыми. Вы узнали, почему ручная обработка HTML-форм — это боль, и
как Django элегантно решает проблемы валидации, приведения типов и безопасности
(CSRF).
Мы научились создавать формы с помощью forms.Form, переопределять виджеты для
настройки внешнего вида, писать собственные сложные правила валидации (с1еап_методы)
и правильно организовывать логику представлений с использованием паттерна
Post/Redirect/Get.
Наконец, мы познакомились с магией ModelForm, которая позволяет за пару строк кода
создавать мошные интерфейсы для добавления и редактирования записей в базе данных,
строго следуя принципу DRY.
Но пока вся наша бизнес-логика (обработка GET и POST, проверка валидности) пишется в
виде громоздких функций (FBV — Function-Based Views) с кучей условий if/else. Это
нормально для начала, но Django предлагает более продвинутый, структурированный и
переиспользуемый подход. В 6 главе мы перейдем на новый уровень абстракции и изучим
Представления на основе классов (Class-Based Views).
Глава 6: Представления на основе
классов (Class-Based Views)
До сих пор во всех предыдущих главах мы использовали функции для написания логики
обработки веб-страниц. Такие представления называются Function-Based Views (FBV). Это
классический, интуитивно понятный подход: запрос заходит в функцию, функция
выполняет проверки условий, извлекает данные из базы, рендерит шаблон и возвращает
ответ.
Однако по мере роста веб-приложения вы начнете замечать, что в разных функциях
повторяется один и тот же шаблонный код. Вам постоянно приходится писать громоздкие
конструкции if request.method == "POST":, вручную обрабатывать формы, настраивать
пагинацию списков и обрабатывать исключения вроде отсутствия объекта в базе данных
(Http404). Это приводит к раздуванию кодовой базы и нарушению принципа DRY.
Для решения этой проблемы в Django был внедрен мощный инструмент — Представления
на основе классов (Class-Based Views, или CBV). В этой главе мы глубоко погрузимся в мир
объектно-ориентированного подхода к обработке запросов, разберем разницу между
функциями и классами, изучим богатый арсенал встроенных базовых классов (Generic
Views) и научимся переопределять их внутренние методы для реализации любой
нестандартной бизнес-логики.
6.1, Разница между функциями-представлениями (FBV) и
классами-представлениями (Cbv)
Чтобы понять, зачем нужны классы-представления, давайте проведем прямое сравнение
архитектурных подходов. Главное различие кроется в способе организации кода и
механизме переиспользования логики.
Функции-представления (FBV): Простота и ясность
Преимущество функций в том, что они просты для чтения. Вы открываете код функции и
линейно, строка за строкой, видите всё, что происходит с запросом. Здесь нет скрытой
магии. Однако, когда функция начинает обрабатывать и GET-запросы (отображение
страницы), и POST-запросы (валидация формы, отправка писем, редирект), её код
превращается в кашу из условных операторов.
Пример типичной FBV для редактирования статьи:
def edit_post(request, pk):
post = get object_or_404(Post, pk=pk)
if request.method == 'POST':
form = PostForm(request.POST, instance-post)
if form.is_vaiid():
form.save()
return redirect('post-detail', pk-post.pk)
el se:
form = PostForm(instance=post)
return render(request, 'blog/post form.html', {'form': form,
'post': post})
Классы-представления (CBV) Сила наследования и модульности
CBV предлагают альтернативный подход. Вместо разделения логики с помощью условного
оператора if внутри одной функции, класс разделяет обработку разных типов
HTTP-запросов (GET, POST, PUT, DELETE) по отдельным методам класса, которые
называются так же: get(), post(J и так далее. Это делает код чистым и структурированным.
Но самое главное преимущество CBV — это наследование. Разработчики Django заметили,
что большинство задач в веб-разработке стандартны. Они написали базовые классы, в
которых вея эта рутинная логика уже реализована под капотом. Вам остается лишь
унаследовать нужный класс и декларативно указать параметры.
Пример аналогичной логики редактирования статьи на CBV:
from django.views.generic import UpdateView
from .models import Post
from .forms import PostForm
class PostUpdateView(UpdateView):
model = Post
form_class = PostForm
template name = 'blog/post form.html'
success_url = '/blog/' # Куда перенаправить после успеха
Посмотрите на этот код: в нем нет ни одного условия, ни одного вызова метода валидации
или сохранения базы данных! Всего 6 строк декларативного кода полностью заменяют всю
функцию со сложной логикой. Вся магия скрыта внутри родительского класса UpdateView.
Маршрутизация для CBV
Поскольку система маршрутизации Django (файл urls.py) спроектирована так, чтобы
принимать исключительно функции, вы не можете просто передать имя класса в функцию
path(). Для этого у каждого класса-представления есть специальный ст атический метод
as_view(), который превращает класс в функцию.
# urls.py
from django.urls import path
from .views import PostUpdateView
urlpatterns = [
# Метод as view() обязателен при регистрации CBV
path('post/<int:pk>/edit/', PostUpdateView.as_view(),
name='post-edit'),
6.2. Использование встроенных generic views: TemplateView, ListView,
DetailView
1. 'TemplateView: Отображение статических страниц
Если вам нужно просто отрендерить HTML-шаблон, который не делает сложных запросов к
базе данных (например, страница "О нас"), используйте TemplateView.
from django.views.generic import Templateview
class AboutView(TempiateView):
template_name = "blog/about.html"
2. ListView: Вывод списков данных (каталоги, блоги)
Этот класс автоматизирует и ?влечение списка объектов из базы данных, передачу его в
контекст шаблона и настройку постраничной навигации (пагинации).
from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/index.html'
context_cbject name = 'posts'
paginate _bу =5 # Автоматическая пагинация: по 5 статей на
страницу!
3. DetailView: Детальная страница объекта
DetailView предназначен для отображения информации об одном конкретном объекте из
базы данных (например, текст статьи). Он автоматически ожидает получить в
URL-маршруте переменную с именем рк или slug, делает запрос к базе данных и, если
объект не найден, сам выбрасывает ошибку 404.
from django.views.generic import Detailview
class PostDecailView(Detailview) :
model = Post
tempiate_name = 'blog/post_detail.html'
context object name = 'post' # По умолчанию будет доступно как
'object' или 'post'
6.3. Формы и редактирование через CreateView, UpdateView,
DeleteView
1. CreateView. Создание новых записей
Этот класс отвечает за генерацию формы, валидацию данных при отправке POST-запроса и
автоматическое сохранение новой записи в базу данных.
from django.views.generic import CreateView
from .models import Post
from .forms import PostForm
class PostCreateView(CreateView):
model = Post
form class = PostForm
template_name = 'blog/post form.html’
success jrl = '/blog/'
2. UpdateView: Редактирование существующих записей
Работает абсолютно так же, как и CreateView, но требует передачи рк в URL-адресе. Он
автоматически извлекает нужный объект из базы данных по его JD, заполняет поля формы
текущими значениями объекта, а при отправке формы обновляет именно эту запись.
3. DeleteView: Удаление записей
Безопасное удаление данных требует подтверждения. DeleteView при GET-запросе
отображает страницу с вопросом "Вы уверены, что хотите удалить этот объект?", а при
отправке POST-запроса на этот же URL производит физическое удаление записи из БД.
from django.views.generic import DeleteView
from django.urls import reverse_lazy
class PostDeleteView(DeleteView):
model = Post
tempiate_nane = 'blog/post 3onfirm_delete.html'
success url = reverse_lazy('blog-home')
6.4. Переопределение методов (get_ccntext_data, get_queryset,
form_valid)
1 . Метод get_queryset(): Динамическая фильтрация данных
По умолчанию ListView берет абсолютно все записи модели. Но что, если на главной
странице мы хотим показывать только те статьи, у которых флаг is_published равен True?
Для этого мы переопределяем метод get_queryset().
class PublishedPostListView(Listview):
model = Post
template_name = 'blog/index.html'
context )bject_name = 'posts'
def get_queryset(self):
return
Post.objects.filter(is_published=True).order_by('-created_at')
2 Метод get_context_data() Передача дополнительных переменных в шаблон
Если в шаблоне нам нужны дополнительные данные из других моделей (например,
категории для сайдбара), мы переопределяем метод get_context_data().
from .models import Category
class PostListViewWithSidebar(Listview):
model = Post
template_name = 'blog/index.html'
contexc_object_name = 'posts'
def get_context_data(self, **kwargs):
context = super().get context data(**kwargs)
context['all categories'] = Category.objects.all()
return context
Важно: вызов superQ.get_context_data(**kwargs] обязателен! Иначе вы сотрете данные
родительского класса.
3. Метод form_valid{): Перохиат успешной отправки формы
Метод form_valid(J вызывается автоматически, когда отправленная форма успешно прошла
валидацию. Здесь удобно, например, добавить текущего пользователя в качестве автора
создаваемой статьи перед сохранением.
class PostCreateViewWithAuthor(CreateVicw):
model = Post
form_c.l.ass = PostForm
template_name = 'blog/post_form.. html'
success_url = '/blog/'
def form valid(self, form):
form.instance.author = self.request.user
return super().form valid(form)
Итоги 6 главы
Вы освоили мощную архитектурную концепцию Django! Мы разобрали встроенные
классы-представления: TeniplateView, ListView, DetailView для вывода данных и
автоматизации пагинации. Вы научились генерировать CRUD интерфейсы с CreateView,
UpdateView и DeleteView. А также убедились в невероятной гибкости этого подхода:
переопределяя встроенные методы (get_queryset(J, get_context_dataO, form_validQ), вы
получаете полный контроль над логикой вашего веб-приложения.
Глава 7: Аутентификация и
авторизация пользователей
До этого момента наше приложение было полностью открытым. Любой посетитель мог
зайти на сайт, прочитать статьи и, если бы мы оставили ссылки на страницы
редактирования, мог бы удалить или изменить любую запись. В реальном мире такая
анархия недопустима. Нам нужно знать, кто именно пользуется сайтом, и ограничивать
права доступа в зависимости от статуса пользователя.
К счастью, вам не придется писать сложную криптографию для хэширования паролей или
изобретать механизмы работы с cookies с нуля. Django поставляется с мощной, безопасной
и готовой к использованию системой управления пользователями. В этой главе мы научимся
регистрировать пользователей, выполнять вход в систему, управлять сессиями и защищать
страницы нашего сайта от незваных гостей.
7.1. Встроенная система аутентификации Django
Для начала давайте разберемся с терминологией, которую часто путают: аутентификация и
авторизация.
• Аутентификация (Authentication]: Процесс проверки того, кем является пользователь.
Грубо юворя, эго ответ на вопрос "Ты кто такой?". Обычно осуществляется путем
проверки связки логин/пароль.
• Авторизация (Authorization): Процесс проверки прав пользователя. Это ответ на вопрос
"А есть ли у тебя права делать это?". Например, пользователь аутентифицирован как
"Иван", но авторизован ли он удалять статьи? Скорее всего, нет, если он не
администратор.
Приложение django. contrib. auth
Вся эта магия живет во вст роенном приложении django.contrib.auth Если вы вспомните
Главу 2, когда мы смотрели на список INSTAI.LED_APPS в файле settings.py, вы могли
заметить, что это приложение (и связанное с ним django.contrib.sessions) подключено по
умолчанию.
Сердцем этой системы является модель User (Пользователь). У нее уже есть все
необходимые поля: username (логин), password (пароль, хранящийся в виде необратимого
хэша), email, first_name, last_name, а также флаги is. active (активен ли аккаунт), is_staff
(может ли заходить в админку) и is.superuser (имеет ли все права).
Доступ к текущему пользователю можно получить в любом представлении (view) через
объект запроса: request.user. В шаблонах этот объект 1акже доступен по умолчанию просто
как переменная {{ user }}.
7.2. Создание форм регистрации, входа (login) и выхода (logout)
Чтобы пользователи moi ли взаимодействовать с системой, нам нужны соответствующие
страницы и формы. Мы можем написать их с нуля, но Django предоставляет готовые
решения, которые сэкономят массу времени.
Регистрация: UserCreationFoi m
Для регистрации новых пользователей мы воспользуемся встроенной формой
UserCreationForm. Она автоматически проверяет, не занят ли логин, а также запрашивает
пароль дважды и проверяет его па надежность (не слишком ли он короткий, не совпадает ли
с логином и т.д.).
Создадим класс-представление для регистрации (используя CreateView из прошлой главы):
from django.views.generic import CreateView
from django.contrib.auth.forms import UserCreationForm
from django.urls import reverse_lazy
class RegisterUserView(CreateView) :
form class = UserCreationForm
template_name = 'regrstration/register.html'
# После успешной регистрации перенаправляем на страницу входа
success_url = reverse lazy('login')
Вам останется только создать простой HTML-шаблон register.html, вывести в нем форму ({{
form.as_p )}) и подключить представление в urls.py.
Вход (Login)* Встроенный LoginView
Для входа в систему писать свое представление (view) вообще не нужно! Django
предоставляет готовый класс LoginView, который сам обработает POST-запрос, сверит хэши
паролей и, если все верно, залогинит пользователя.
Все, что нужно — это подключить его в urls.py
from django.urls import path
from django.contrib.auth.views import LoginView
urlpatterns = [
# ... другие пути ...
path('login/',
LoginView. as_view(template name=' registration/1 og.in.html') ,
name='login') ,
]
Обратите внимание: мы передаем template_name прямо в метод as_view(). Если этого не
сделать, Django будет искать шаблон по умолчанию по пути registration/login.html.
Настройка редиректа после лозина: В файле settings.py нужно указать, куда перенаправлять
пользователя после успешного входа, если он просто зашел на страницу /login/.
# settings.py
LOGIT REDIRECT_URL = '/' # Перенаправить на главную страницу
Выход (Logout) Встроенный Logoutview
Выход из системы реализуется аналогично. Класс LogoutView стирает сессию пользователя,
превращая его обратно в анонимного гостя.
from djanqo.contrib.auth.views import Logoutview
urlpatterns = [
path('logout/', Logoutview.as view(next_page='/'), name='logout'),
]
Параметр next_page указывает, куда отправить пользователя после того, как он нажал
кнопку "Выйти".
Отображение состояния в шаблоне
Теперь в базовом шаблоне base.html мы можем использовать свойство
user.is_authenticated, чтобы показывать разные пункты меню для гостей и для
зарегистрирован н ых I юльзовател ей:
<nav>
<ul>
clixa ЬгеТ="/">Главная</ах/11>
{% if user.is_authenticated %}
<И>Привет, {{ user. username }} ! </li>
<li><a href="{% url 'post-create' %}">Нэписать
статью</ax/li>
<!— В новых версиях Django выход делается через
POST-форму для защиты от CSRF —>
<li>
<form action="{% url 'logout' %}" methcd="post">
{% csrf_token %}
ebutton type="submit">BMiiTH</button>
</form>
</li>
{% else %}
<liXa href="{% url 'login' %}">Войтл</ах/11>
clixa href="{% url 'register' %}">Регистрация</а></11>
{% endif %}
</ul>
</nav>
7.3. Управление сессиями
Протокол HTTP, на котором работает интернет, является "stateless" (без сохранения
состояния). Это значит, что сервер "забывает" о вас сразу после того, как отдал
HTML-страницу. Как же тогда Django понимает, что вы залогинены, когда вы переходите с
одной страницы на другую?
Ответ: Cookies и Сессии (Sessions).
Как это работает под капотом
1. Когда вы успешно вводите логин и пароль в LoginView, Django генерирует уникальный
случайный набор символов — идентификатор сессии (session ID).
2. Django сохраняет этот ID в своей базе данных в специальной таблице django_session
(которая была создана во время миграций). К этому ID привязывается ваш ID пользователя
(userjd).
3. Сервер отправляет этот session ID в ваш браузер в виде крошечного текстового файла —
Cookie (куки), с инструкцией: "Сохрани это и прикрепляй к каждому следующему запросу
на этот домен".
4. Когда вы открываете следующую страницу, браузер незаметно для вас отправляет Cookie
на сервер. Django читает ID сессии, ищет его в своей базе данных, находит привязанный
userjd и понимает: "Ага, это снова Иван!". Объект request.user заполняется данными
Ивана.
Вам, как разработчику, редко придется взаимодействовать с сессиями напрямую, но важно
понимать этот механизм. Если вы хотите хранить в сессии какие-то свои временные данные
(например, товары в корзине для неавторизованного гостя), вы можете использовать словарь
request.session:
def add_to_cart(request, product_id):
# Если в сессии еще нет корзины, создаем пустой список
if 'cart' not in request.session:
request.session['cart'] = []
# Добавляем товар и сохраняем сессию
request.session['cart'].append(product_id)
request.session.modified = True
return HttpResponse("Товар добавлен!")
7.4. Разграничение доступа: декораторы и миксины
Теперь мы умеем логинить пользователей. Но наши представления (views) для создания и
редактирования статей все еще доступны по прямым URL-адресам кому угодно. Нам нужно
защитить их.
Защита FBV* декоратор @login_required
Если вы используете функции-представления (FBV), для защиты применяется декорат ор
(a)login_required. Декоратор — это "обертка" над функцией, которая изменяет ее поведение.
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
# Эта страница будет доступна только авторизованным пользователям
Cdlogin_required
def secret dashboard(request):
return render(request, 'dashboard.html')
Если неавторизованный гость попытается зайти на эту страницу, декоратор перехватит
запрос и автоматически перенаправит его на страницу входа [указанную в
settings.LOG!N_URL, которую тоже нужно задать). При этом к URL будет добавлен параметр
?next=/secret-dashboard/, чтобы после логина человека вернули туда, куда он изначально
хотел попасть.
# settings.py
LOGI1 URL = 'login' # Имя URL-маршрута страницы входа
Защита CBV; LoginRequircdMixin
Декораторы не работают напрямую с классами. Для защиты Class-Based Views в Django
используются миксины (Mixms) — специальные классы-примеси, которые добавляют
функционал через множественное наследование.
Важно: Миксин должен стоять первым (самым левым) в списке наследования!
from django.contrib.auch.mixins import LoginRequiredMixin
from django.views.generic import CreateView
# Теперь создать статью может только залогиненный юзер
class PostereateView(LoginRequiredMixin, CreateView):
model = Post
form_class = PostForm
template_name = 'biog/post_forrr..html'
Проверка авторства (UserPassesTestMixin)
Залогиниться — это половина дела (Аутентификация). Но мы не хотим, чтобы один автор
мог редактировать или удалять статьи другого автора (Авторизация).
Для сложной проверки прав используется UserPassesTestMixin. Он требует реализации
метода test_func(), который должен вернуть True (доступ разрешен) или False (ошибка 403
Forbidden).
from django.contrib.auth.mixins import LoginRequiredMixin,
UserPassesTestMixin
from django.views.generic import UpdateView
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin,
UpdateView):
model = Post
form class = PostForm
template_name = 'blog/post form.html'
def test func(self):
# 1. Получаем объект статьи, которую пытаются редактировать
current_post = self.get object()
# 2. Проверяем, совпадает ли автор статьи с текущим
пользователем
if self.request.user == current_post.author:
return True
return False
Итоги 7 главы
В этой главе мы сделали огромный шаг в сторону безопасности и персонализации нашего
веб-приложения. Мы разобрались с терминами аутентификации и авторизации и
познакомились с могущественным приложением django.contrib.auth.
Вы научились использовать готовые формы (UserCreationForm) и представления
(LoginView, LogoutView) для быстрой настройки системы входа и регистрации, избежав
написания десятков строк рутинного кода. Мы заглянули "под капот" протокола HTTP и
узнали, как cookies и сессии позволяют серверу "помнить" пользователей.
Наконец, мы надежно закрыли критические разделы нашего сайта от неавторизованного
доступа с помощью декоратора (®login_required для функций и классов
LoginRequiredMixin и UserPassesTestMixin для CBV. Теперь наш сайт полностью защищен:
гости могут только читать, пользователи могут писать свои статьи, а авторы могут
редактировать только свои собственные публикации.
Архитектура нашего проекта почти завершена, он функционирует как часы. Но как
убедиться, что мы случайно не сломаем его при добавлении новых фич в будущем? В 8
главе мы познакомимся с профессиональным стандартом разработки — написанием
автоматизированных тестов.
Глава 8: Тестирование проекта
Большинство начинающих разработчиков не любят писать тесты. Кажется, что это пустая
трата времени: "Зачем писать дополнительный код, который ничего не делает для конечного
пользователя, если я могу просто открыть браузер, нажать пару кнопок и своими глазами
убедиться, что всё работает?".
Этот подход работает ровно до тех пор, пока ваш проект состоит из двух страниц. Но когда
страниц становится пятьдесят, а вы вносите изменение в базовую логику базы данных,
проверить вручную все возможные сценарии становится физически невозможно. Вы
исправляете один баг, но случайно ломаете функционал в совершенно другом месте (это
называется "регрессия"). В итоге разработка превращается в сэрах нажать кнопку
"Сохранить".
Автоматизированное тестирование — это ваша подушка безопасности. В этой главе мы
научимся писать код, который проверяет вант код. Мы разберем встроенные инструменты
Django для тестирования моделей и представлений, чтобы вы могли уверенно развивать и
масштабировать свои проекты.
8.1. Зачем писать тесты: введение в TDD
Автоматизированные тесты решают сразу несколько фундаментальных проблем
разработки:
• Защита от регрессий: Тесты гарантируют, что новый код не сломал старый функционал.
• Документация: Хорошо написанный тест наглядно показывает, как именно должен
работать тот или иной участок кода.
• Упрощение рефакторинга: Вы можете смело переписывать внутреннюю архитектуру
приложения. Если после изменений тесты горят "зеленым", значит, для конечного
пользователя ничего не сломалось.
Что такое TDD?
В профессиональной среде часто используется подход TDD (Test-Driven Development —
разработка через тестирование). Его мантра звучит как Red-Green-Refactor
• Red (Красный): Вы пишете тест для функции, которой еще не существует. Естественно,
тест падает (горит красным).
* Green (Зеленый): Вы пишете минимально возможный и самый простой код, чтобы тест
прошел успешно.
* Refactor (Рефакторинг): Теперь, когда у вас есть защита в виде работающего теста, вы
улучшаете написанный код, делая его более элегантным и оптимальным.
Даже если вы не будете использовать чистый TDD, написание тестов параллельно с
написанием кода (или сразу после) является обязательным стандартом индустрии.
8.2. Встроенный фреймворк тестирования Django
Django не изобретает велосипед с нуля. Ею подсистема тестирования построена поверх
стандартной библиотеки Python под названием unittest.
Файл tests.py и класс TestCase
Когда мы создавали приложение с помощью команды startapp, Django заботливо
сгенерировал для нас файл tests.py. Озкроем его. Для написания тестов мы должны
импортировать класс TestCase из django.test.
from django.test import TestCase
# Create your tests here.
Класс TestCase в Django — это не просто стандартный питоновский unittest.TestCase. У
него есть одна суперспособность: изоляция базы данных. Каждый раз, когда вы запускаете
тесты, Django создает абсолютно новую, пустую базу данных (обычно в оперативной
памяти). Затем он применяет все ваши миграции, прогоняет тесты и после этого уничтожает
эту тестовую базу.
Это означает, что: 1) Ваши тесты не замусорят вашу реальную базу данных (db.sqlite3). 2)
Тесты не зависят друг от друга. Если один тест удалил статью, для следующего теста статья
снова будет существовать, если вы ее там создадите.
8.3. Написание тестов для моделей и представлений
Давайте напишем наши первые тесты. Правило именования: любой метод внутри класса
TestCase, который должен быть выполнен как тест, обязан начинаться со слова test_
(например, test_model_creation).
Тестирование моделей
Что мы хотим проверить в модели Post? Во-первых, что объект корректно создается.
Во-вгорых, что наш метод _str_ возвращает правильную строку (заголовок статьи).
from django.test import TestCase
from .models import Post
class PostMcdelTest(TestCase):
# Метод setup выполняется ПЕРЕД каждым тестом.
# Здесь удобно создавать начальные данные,
def setup(self):
self.post = Post.objects.create(
title=1 Тестовая статья',
content='Текст для теста'
)
def testpostcreation(self):
# Проверяем, что статья действительно появилась в БД
self.assertEqual(Post.objects.count(), 1)
def test post_title nax_length(self):
# Получаем поле из метаданных модели и проверяем параметр
max_length
max Length = self.post._meta.get_field('title').maxlength
self.assertEqual (rr.ax_length, 200)
def test_post_str method(seif):
# Проверяем метод str
self.assertEqual(str(self.post), 'Тестовая статья')
Мы используем методы семейства assert (утверждать), унаследованные от unittest.
assertEqual проверяет равенство двух значений. Если они не равны, тест завершается с
ошибкой.
Тестирование представлений (Views)
При тестировании представлений нас интересует, как приложение отвечает на
HTTP-запросы. Для имитации работы браузера Django предоставляет специальный класс
Client.
Давайте проверим главную страницу нашего блога (допустим, это PostListView, доступный
по корневому URL"/").
from django.test import TestCase
from django.urls import reverse
from .models import Post
class BlogViewsTest(TestCase):
def setup(self):
t* Создаем статью для отображения
Post.objects.create(title='Моя статья', content='Контент')
def test_homepage_status_code(self):
# Делаем GET-запрос на главную страницу (используем reverse
для получения URL по имени)
response = self.client.get(reverse('blog-home'))
# Проверяем, что сервер ответил кодом 200 СК (страница
существует и загрузилась)
self.assertEqual(response.status_code, 200)
def test__nomepage uses_correct_template(self):
response = self.client.get(reverse('blog-home'))
# Проверяем, что использовался правильный HTML-шаблон
self.assertTemplateUsed(response, 'blog/index.html')
def test_homepage contains correct html(self):
response = self.client.get(reverse('blog-home'))
# Проверяем, что в HTML-коде ответа есть текст нашей статьи
self.assertContains(response, 'Моя статья')
Здесь мы использовали новые проверки: assertTemplatellsed (проверяет, какой файл
шаблона был отрендерен] и assertContains (ищет подстроку в итоговом HTML-ответе].
8.4. Запуск тестов и анализ результатов
Мы написали код тестов. Как заставить Django их выполнить?
Команда test
Откройте терминал и выполните специальную команду управления:
python manage.py test
Вы можете запустить тесты только для конкретного приложения (python manage.py test
blog] или даже только один конкретный тестовый класс (python manage.py test
Ы og. tests. Bl ogViewsTest].
Чтение консольного вывода
При запуске тестов вы увидите в консоли строку из точек:
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
Ran 6 tests in O.C45s
OK
Destroying test database for alias 'default'...
Каждая точка (.) означает один успешно пройденный тест. Если тест завершается неудачно
(assert не совпал], вместо точки появится буква F (Failure], Если внутри кода теста
произошла системная ошибка (например, опечатка в имени переменной], появится буква Е
(Error],
В случае ошибки (F или Е] Django выведет подробный Traceback, показывающий, на какой
именно строке какого файла тест споткнулся, и какое значение ожидалось вместо
полученно! о.
Покрытие кода тестами (Coverage)
Когда проект разрастается, возникает вопрос: "А все ли важные части моего кода покрыты
тестами?". Для ответа на этот вопрос в экосистеме Python существует популярная утилита
coverage. Она анализирует выполнение программы во время тестов и показывает процент
проверенного кода.
Установив библиотеку (pip install coverage], вы можете запускать тесты через нее: coverage
run manage.py test. Затем команда coverage report (или coverage hi ml для красивого отчета
в браузере) покажет вам статистику, например: "В файле models.py покры го 100% кода, а в
views.py — только 60%, вы забыли протестировать ветку if request.method == POST1'.
Стремиться к 100% покрытию не всегда целесообразно, но покрытие ниже 70% для
серьезного проекта — повод задуматься.
Итоги 8 главы
В этой главе мы освоили процесс, который отличает программистов-любителей от
профессиональных инженеров. Вы узнали, почему ручное тестирование в браузере
неэффективно в долгосрочной перспективе, и познакомились с философией TDD.
Мы изучили класс TestCase в Django, поняли, как фреймворк изолирует базу данных для
безопасного тестирования, и написали тесты для проверки атрибутов моделей и
правильности работы представлений с помощью объекта Client. Теперь вы умеете запускать
тесты, читать отчеты об их выполнении и гарантировать надежность своего кода.
Наш проект полностью готов, защищен паролями и покрыт тестами. Остался последний,
самый волнующий шаг. В 9 главе мы вытащим наш сайт из уют ной среды локального
компьютера и развернем его па реальном сервере в интернете, чтобы его мог увидеть весь
мир!
Глава 9: Развертывание
(Deployment) проекта на сервере
До этого момента ваш код жил исключительно на вашем компьютере. Вы запускали
локальный сервер разработки (python manage.py runserver) и любовались результатом по
адресу http://127.0.0.1:8000. Но 1лавная цель веб-разработки — сделать сайт доступным
для всего мира.
Процесс переноса кода с локального компьютера на публичный сервер называется
развертыванием (Deployment, или "деилой"). Это один из самых сложных и ответственных
этанов, потому чго то, чт о хорошо работает на вашем ноутбуке, может легко сломаться под
нагрузкой реальною интернета, если нс настроить правильную архитектуру.
В этой заключительной главе мы подготовим наш проект к реальной жизни: отключим
опасные настройки отладки, спрячем секретные ключи, заменим игрушечную базу данных
SQLite на промышленную PostgreSQL, научим сервер правильно отдавать статические
файлы и выложим проект на современную платформу облачного хостинга.
9.1, Разница между сервером разработки и продакшен-сервером
Почему мы не можем просто запустить "runserver" на реальном сервере, открыть порт 8000
и пригласит ь пользоваюлей?
• runserver не умеет держать нагрузку: Встроенный сервер Django однопоточный. Если
два пользователя зайдут на ваш сайт одновременно, второй будет ждать, пока сервер не
закончит обрабатывать запрос первого. При десяти посетителях сайт просто зависнет.
• runserver не умеет раздавать статику эффективно: Раздача картинок, CSS и JS файлов
через Python — это невероятно медленно. Этим должны заниматься специальные
веб-серверы (например, Ngmx).
• runserver небезопасен: Он не предназначен для защиты от DDoS-атак или специфичных
НТТР-уязвимостей.
Архитектура "Продакшен" (боевого сервера) выглядит иначе. Вместо одного runserver
используются три компонента:
1. Web Server (например, Nginx): Принимает все запросы от пользователей. Если
запрашивают карг инку (статику), он отдает ее сам, мгновенно. Если запрашивают
динамическую HTML-страницу, он передает запрос дальше.
2. WSGI/ASGI Server (например, Gunicorn или uWSGI): Это "переводчик" между
веб-сервером Nginx и вашим Python-кодом. Он умеет запускать несколько копий вашего
приложения (воркеров), обрабатывая множество запросов параллельно.
3. Само приложение Django: Получает переведенный запрос, лезет в базу данных,
генерирует HTML и отдает его обратно по цепочке.
9.2. Подготоика настроек: DEBUG, ALLOWED_HOSTS И SECRET_KEY
Перед деплоем необходимо критически пересмотреть файл settings.py. Настройки для
локальной разработки категорически не подходят для боевою сервера.
Переменные окружения (Environment Variables)
Главное правило безопасности: никогда не храните пароли и секретные ключи в коде
(особенно если вы выкладываете его на Git Hub). Для этого используются переменные
окружения. Чтобы удобно работать с ними локально, мы установим библиотеку
python-dotenv:
pip install python-dotenv
В корне проекта создайте файл .env (с точкой в начале) и добавьте туда секреты. Этот файл
нужно обязательно добавить в gitignore, чтобы он не попал в репозиторий!
# файл .env
SECRFT_KFY=d j ango-i пзесиге-ваша-секретная-строка
DEBUG=False
Теперь обновим settings.py, чтобы он читал эти настройки из файла .env:
import os
from pathlib import Path
from dotenv import ioad_dotenv
BASC_DIR = Path(__file ). resolve () .parent .parent
# Загружаем переменные из .env
load_dotenv(os.path.join(BASE DIR, '.env'))
# Читаем SECRET_KEY
SECRETKEY = 03.environ.get('SECRET_KEY')
# В продактене DEBUG обязан быть False!
DEBUG = os.environ.дед('DEBUG', 'False') == 'True'
Почему DEBUG = False гак важен?
Когда DEBUG = True, при любой ошибке (например, делении на ноль) Django выводит
красивую желтую страницу со всеми подробностями: строками вашего кода, sql-запросами
и, что самое страшное, значениями всех переменных. Хакер может легко спровоцировать
ошибку и украсть пароль от вашей базы данных. При DEBI IG = False пользователи будут
видеть только стандартную страницу "500 Server Error", не раскрывающую внутренности
приложения.
ALLOWED_HOSTS
Как только вы выключаете DEBUG, Django требует обязательно заполнить список
ALLOWEDJIOSTS. Это защита от азак с подменой HTTP-заголовка Host.
# Укажите реальное доменное имя вашего сайта
ALLOWED_HOSTS = ['my-awesome-blog.com', 'www.my-awesome-blog.com',
'127.0.0.1']
9.3. Настройка базы данных для продакшена (PostgreSQL)
SQLite — отличная база данных, но она хранится в одном файле. В облачных средах
(например, на Heroku или Render) файловая система эфемерна: при каждом перезапуске
сервера (деплое) ваш файл db.sqlite3 будет безвозвратно удален! Нам нужна полноценная
клиент-серверная СУБД — PostgreSQL.
Пакет dj-database-url
В облаке параметры подключения к БД (пользователь, пароль, хост) обычно
предоставляются в виде одной длинной строки (URL). Чтобы Django легко ее понял,
установим пакеты:
pip install dj-database-url psycopg2-binary
Теперь модифицируем настройки базы данных в settings.py:
import d j_aar.abase_url
# По умолчанию оставляем SQLite для локальной разработки
DATABASES = {
'default': {
'ENGINE': ' django.db.backends.sglite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Но если в переменных окружения есть DATABASE_'JRL (а на сервере ока
будет),
# то заменяем настройки на PostgreSQL
db_from ?nv = dj_database_url.config(conn_max age=600)
DATABASES [ ' default' J . update (db_f roir._env)
9.4. Работа co статикой в продакшене (WhiteNoise)
Когда мы отключаем DEBUG=False, Django перестает сам раздавать статические файлы
(CSS, JS, картинки). Если вы выложите проект так, сайт будет выглядеть сломанным — без
стилей и изображений.
Команда collectstatic
В реальном проекте статика может лежать в разных местах: в ваших папках static/, внутри
приложения admin (стили админки) или в сторонних библиотеках. Команда collectstatic
собирает абсолюшо всю статику со всего проекта и копирует ее в одну единственную папку,
из которой Nginx (или другой веб-сервер) сможет быстро ее раздавать.
# В settings.py указываем папку, куда все будет собрано:
STATIC_ROOT = BASE DIR / 'staticfiles'
Использование WhiteNoise
Настроить Nginx с нуля бывает сложно. Для современных облачных платформ (PaaS)
идеальным решением является библиотека WhiteNoise. Она позволяет самому
Python-серверу (Gumcorn) раздавать статику невероятно эффективно, с кэшированием и
сжатием.
pip install whitenoise
Добавляем WhiteNoise в список MIDDLEWARE сразу после SecurityMiddleware:
MIDDLEWARE = [
'd j anqo.middleware.securit у.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', it ВАЖНО: Добавить
сюда
'django.contrib.sessions.middleware.SessionMiddleware',
# ...
]
9.5. Варианты хостинга: пример разверти :ания
Раньше разработчикам приходилось покупать "голый" сервер (VPS на Ubuntu),
устанавливать туда Python, базу данных, Nginx, писать системные службы и настраивать
сертификаты. Сегодня популярны платформы PaaS (Platform as a Service), такие как
Render, Railway или Python Anywhere, которые автоматизируют 90% этой работы.
Подготовка файла зависимостей (requirements.txt)
Сервер должен знать, какие библиотеки установить для работы вашего проекта. Для этою
мы генерируем список всех пакетов нашего виртуального окружения:
pip freeze > requirements.txt
В этом файле будут перечислены Django, psycopg2- binary, gunicorn, whitenoi.se и другие
библиотеки с указанием их версий.
Скрипт сборки (build.sh)
На сервере нам нужно выполнить миграции и собрать статику. Создайте в корне проекта
файл build.sh:
#1/usr/bin/env bash
# Выходим при любой ошибке
set -о errexit
pip install -г requirements.txt
python manage.py collectstatic --no-input
python manage.py migrate
Убедитесь, что вы сделали файл исполняемым, если работаете на Linux/Мас: chmod 4-х
build.sh
Настройка Gunicorn
Установите Gunicorn — наш WSGI сервер:
pip install gunicorn
Деплой на Render (краткий обзор)
Render.com — одна из лучших бесплатных платформ для старта. Процесс деплоя сводится к
нескольким кликам:
1. Загрузите ваш код на GitHub (не забудьте добавить файл .env и папку venv в .gitignore!).
2. Зарегистрируйтесь на Render и создайте новую базу данных PostgreSQL (скопируйте
выданный Internal Database URL).
3. Создайте ''Web Service'', подключив ваш репозиторий GitHub.
4. В настройках сервиса укажите Build Command: /build.sh
5. Укажите Start Command: gunicorn inyproject.wsgrapplication (замените myproject на
имя папки, где лежит wsgi.py).
6. В разделе Environment Variables добавьте: DATABASE_URL (вставьте ссылку из шага 2),
SECRET_KEY (придумайте новую строку) и DEBUG-False.
Нажмите "Save", и Render сам скачает ваш код, установит библиотеки, применит мш рации
базы данных и запустит Gunicorn. Через 2-3 минуты вы получите публичную ссылку на ваш
рабочий проект!
Заключение
Поздравляю! Вы прошли огромный путь. От установки Python и создания пустого каркаса
(Глава 1) до маршрутизации, шаблонов, работы с базами данных (Глава 4], обработки форм
(Глава 5J и продвинутых классов-представлений (Глава 6). Вы защитили пользователей
надежной системой аутентификации (Глава 7], написали автотесты (Глава 8] и, наконец,
выпустили свой проект в большой интернет.
Что изучать дальше?
• Django REST Framework (DRF): Для создания API, если вы захотите написать
мобильное приложение для вашего сайта или использовать современные JS-фреймворки
(React/Vue) на фронтенде.
• Celery: Для выполнения фоновых задач (например, массовая рассылка писем), чтобы не
заставлять пользователя ждать загрузки страницы.
• Docker: Для контейнеризации приложения, чтобы навсегда забыть проблему "А на моем
компьютере это работало!".
Вы освоили мощнейший инструмент веб-разработки. Теперь все ограничено только вашей
фант азией. Успехов в написании великолепного кода!