Найти в Дзене
AERONYTE

Создание Блога на Django 5, PostgreSQL 16 с полнотекстовым поиском

Эта статья подготовлена по мотивам книги «Django 5 By Example» (автор: Antonio Melé, издательство: Packt Publishing, 5-е издание, ISBN: 9781805122340). Статья не является официальным переводом книги и не копирует оригинальный текст. Все материалы в публикации — это мои личные переработки, пояснения, примеры и улучшения, сделанные с целью образовательной поддержки русскоязычного сообщества разработчиков. Примеры кода, использованные в статье, основаны на открытом репозитории книги и распространяются по лицензии MIT, допускающей свободное использование с указанием авторства. Все права на оригинальное содержание принадлежат автору книги и издательству Packt Publishing. 📖 Оригинальная книга доступна на официальном сайте:
https://www.packtpub.com/en-us/product/django-5-by-example-9781805122340 В этой статье вы научитесь создавать веб-приложение блог с использованием фреймворка Django. Шаг за шагом вы познакомитесь с основными строительными блоками любого Django-проекта — от установки фрей
Оглавление

⚠️ Дисклеймер

Эта статья подготовлена по мотивам книги «Django 5 By Example» (автор: Antonio Melé, издательство: Packt Publishing, 5-е издание, ISBN: 9781805122340).

Статья не является официальным переводом книги и не копирует оригинальный текст. Все материалы в публикации — это мои личные переработки, пояснения, примеры и улучшения, сделанные с целью образовательной поддержки русскоязычного сообщества разработчиков.

Примеры кода, использованные в статье, основаны на открытом репозитории книги и распространяются по лицензии MIT, допускающей свободное использование с указанием авторства.

Все права на оригинальное содержание принадлежат автору книги и издательству Packt Publishing.

📖 Оригинальная книга доступна на официальном сайте:

https://www.packtpub.com/en-us/product/django-5-by-example-9781805122340

Введение

В этой статье вы научитесь создавать веб-приложение блог с использованием фреймворка Django.

Шаг за шагом вы познакомитесь с основными строительными блоками любого Django-проекта — от установки фреймворка до развёртывания на сервере.

Что вы изучите в этой статье

Перед тем как приступить к разработке первого проекта на Django, давайте кратко пройдёмся по темам, которые мы затронем.

В этой статье вы получите общее представление о фреймворке Django и познакомитесь с его основными компонентами:

  • моделями данных (models),
  • шаблонами (templates),
  • представлениями (views),
  • и маршрутами (URLs).

Вы поймёте, как работает Django "изнутри" и как между собой взаимодействуют ключевые части фреймворка.

Также вы узнаете:

  • чем отличаются проекты и приложения Django;
  • как устроены основные настройки конфигурации (settings.py).

Что вы создадите

Вы разработаете простое, но функциональное веб приложение блога, в котором пользователь сможет:

  • просматривать список опубликованных постов;
  • открывать и читать отдельные статьи;
  • а также — через административный интерфейс Django — создавать, редактировать и публиковать записи.

В следующих двух статьях вы расширите функциональность блога, добавив пагинацию, формы, систему комментариев и многое другое.

Рассматривайте эту статью как дорожную карту по созданию полноценного Django-приложения с нуля.

Не переживайте, если какие-то компоненты или концепции сначала покажутся не до конца понятными — в процессе изучения последующих статей вы разберётесь во всех аспектах фреймворка, поскольку каждый ключевой компонент будет подробно рассмотрен в следующих статьях.

В этой статье вы изучите:

  • Установку Python;
  • Создание виртуального окружения Python;
  • Установку Django;
  • Создание и настройку Django-проекта;
  • Разработку Django-приложения;
  • Проектирование моделей данных;
  • Создание и применение миграций моделей;
  • Настройку административной панели Django для работы с моделями;
  • Работа с QuerySet и менеджерами моделей;
  • Построение представлений (views), шаблонов (templates) и маршрутов (URLs);
  • Понимание цикла обработки запроса и ответа в Django (request/response cycle).

Начнем с установки Python на ваш компьютер.

Исходный код для этой статьи доступен по адресу:

https://github.com/myasoedas/Django-5/

Установка Python

Веб фреимворк Django 5.0 поддерживает Python версий 3.10, 3.11 и 3.12. Во всех примерах этой статьи и последующих статей используется Python 3.12.

Если вы работаете в Linux или macOS, скорее всего Python у вас уже установлен. Если вы используете Windows, скачайте установщик Python 3.12 с официального сайта:

https://www.python.org/downloads/release/python-31210/

Как открыть терминал

macOS: Нажмите Command + Пробел, введи Terminal и выберите Terminal.app.

Windows: Откройте меню «Пуск» и введите в поиск:

  • powershell → выберите Windows PowerShell
  • или cmd → выберите Командная строка (Command Prompt)

Обе программы позволяют работать с Python через терминал.

Убедитесь, что Python 3 установлен на вашем компьютере.

Для этого введите в терминале следующую команду:

python3 --version

Если в ответ вы видите что-то вроде следующего:

Python 3.12.7

Если команда python3 вызывает ошибку, попробуйте использовать просто python:

python --version

Для пользователей Windows рекомендуется вместо python использовать:

py --version

Если установленная версия Python меньше 3.12 или Python вовсе не установлен, скачайте Python 3.12 с официального сайта:

https://www.python.org/downloads/release/python-31210/

На странице загрузки вы найдёте установщики для всех популярных платформ: Windows, macOS и Linux.

Следуйте пошаговой инструкции на официальном сайте Python для установки Python 3.12 под свою операционную систему (Windows, macOS, Linux).

В этой и последующих статьях, когда мы будем обращаться к Python в терминале, мы будем использовать команду:

python

Однако на некоторых системах может потребоваться команда python3.

Если вы используете Linux или macOS, и по умолчанию у вас установлен Python 2, то для запуска Python 3 нужно использовать именно python3.

Обратите внимание: поддержка Python 2 завершилась в январе 2020 года.
Использовать его в новых проектах не рекомендуется.

В Windows команда python указывает на "основную" установленную версию Python, а команда py — это Python Launcher, специальная утилита, которая появилась, начиная с Python 3.3. Она автоматически определяет установленные версии Python и запускает самую новую из них.

Рекомендация для пользователей Windows: используй команду:

py

Больше информации о Python Launcher для Windows смотрите здесь:

https://docs.python.org/3.12/using/windows.html

Далее вы создадите изолированное окружение Python (virtual environment) для своего проекта и установите необходимые библиотеки.

Создание виртуального окружения Python - venv

При разработке Python-приложений вы будете использовать дополнительные библиотеки и модули, которые не входят в стандартную библиотеку Python.

Иногда разные проекты требуют разные версии одних и тех же пакетов.

Однако в системе одновременно может быть установлена только одна версия модуля.

Если вы обновите библиотеку для одного проекта — это может привести к сбоям в других проектах, где используется более старая версия этого же пакета.

Решение: виртуальные окружения venv

Для решения этой проблемы в Python используется механизм виртуальных окружений (virtual environments).

Виртуальное окружение позволяет устанавливать пакеты в изолированную директорию venv, не затрагивая глобальные (системные) библиотеки Python.

Каждое окружение:

  • имеет свою собственную копию интерпретатора Python;
  • использует отдельную директорию site-packages для установки библиотек;
  • может содержать собственный, независимый набор зависимостей.

Создание виртуального окружения с помощью venv

Начиная с версии Python 3.3, в стандартную поставку входит модуль venv, позволяющий создавать лёгкие изолированные виртуальные окружения.

Использование venv даёт вам возможность:

  • устанавливать разные версии библиотек для разных проектов;
  • избегать конфликтов между зависимостями;
  • работать без прав администратора, так как все библиотеки устанавливаются локально.

Создание виртуального окружения

Если вы используете Linux или macOS, создайте виртуальное окружение командой:

python3.12 -m venv my_env

Если вы работаете в Windows, используйте Python Launcher и запустите:

py -3.12 -m venv my_env

Эта команда создаст папку my_env со своим интерпретатором Python и папкой site-packages, где будут храниться зависимости проекта.

Активация виртуального окружения venv

Предыдущая команда создала виртуальное окружение Python в новой директории под названием my_env.

Все библиотеки, установленные в виртуальном окружении my_env, будут помещаться в папку:

my_env/lib/python3.12/site-packages

или в Windows:

my_env\Lib\site-packages

Активация виртуального окружения my_env

В Linux/macOS:

source my_env/bin/activate

В Windows (cmd или PowerShell):

.\my_env\Scripts\activate

После активации в командной строке вы увидите имя виртуального окружения my_env в круглых скобках:

(my_env) alex@pc:~$

Это означает, что окружение успешно активировано, и теперь все команды pip, python и т.д. будут выполняться в его контексте.

Деактивация окружения

Чтобы выйти из окружения в любой момент, выполните команду:

deactivate

Подробнее о модуле venv читайте в официальной документации:

https://docs.python.org/3.12/library/venv.html

Установка Django

Django распространяется как Python-модуль, поэтому его можно установить в любое виртуальное окружение Python с помощью стандартного пакетного менеджера pip. Ниже приведена быстрая инструкция, как это сделать на вашем компьютере.

Важно: установка будет производиться внутри активированного виртуального окружения, чтобы изолировать зависимости текущего проекта.

Установка Django с помощью pip

Рекомендуемый способ установки Django — через систему управления пакетами pip. Если вы используете Python 3.12, то pip уже предустановлен.

Если по какой-то причине его нет, инструкции по установке доступны здесь:

https://pip.pypa.io/en/stable/installation/

Команда для обновления pip до последней версии в текущем виртуальном окружении:

python -m pip install --upgrade pip

Если вы используете Windows и Python Launcher, можете явно указать версию:

py -3.12 -m pip install --upgrade pip

После обновления можете проверить версию pip:

pip --version

или

python -m pip --version

Команда установки Django 5.0:

python -m pip install Django~=5.0.4

Эта команда установит последнюю минорную версию Django 5.0.x в директорию site-packages текущего виртуального окружения.

Проверка установки Django

Чтобы убедиться, что Django установлен корректно, выполните в терминале:

python -m django --version

Если вы видите в ответ строку, начинающуюся с 5.0, например:

5.0.14

значит Django установлен успешно.

Что делать при ошибке

Если вы получаете сообщение вида:

No module named django

это означает, что Django не установлен в текущем окружении. Убедитесь, что вы активировали виртуальное окружение перед установкой.

Если возникли проблемы с установкой Django, изучите возможные варианты на официальной странице документации:

https://docs.djangoproject.com/en/5.0/intro/install/

Создание файла зависимостей

Чтобы сохранить текущие зависимости проекта необходимо создать файл requirements.txt, выполнив в корне проекта команду:

pip freeze > requirements.txt

Файл requirements.txt будет выглядеть примерно так:

asgiref==3.8.1
Django==5.0.14
sqlparse==0.5.3

Как установить зависимости проекта Django в другой среде

python3.12 -m venv my_env
source my_env/bin/activate # или .\my_env\Scripts\activate
pip install -r requirements.txt

Обзор Django

Django — это фреймворк, представляющий собой набор компонентов, предназначенных для решения типовых задач веб-разработки.

Компоненты Django слабо связаны между собой (loosely coupled), что означает, что их можно использовать и настраивать независимо.

Это обеспечивает чёткое разделение ответственности между слоями:

  • слой базы данных не знает, как данные будут отображаться;
  • система шаблонов ничего не знает о запросах;
  • обработка запросов не зависит от модели или шаблона.

Принцип DRY и мощь Python

Django поощряет максимальное переиспользование кода, следуя принципу DRY (Don't Repeat Yourself — "Не повторяйся").

Он также поддерживает быструю разработку и позволяет писать меньше кода, используя динамические возможности Python, такие как интроспекция и метапрограммирование.

Подробнее о философии проектирования Django читайте в официальной документации:

https://docs.djangoproject.com/en/5.0/misc/design-philosophies/

Основные компоненты фреймворка Django

Django реализует архитектурный шаблон MTV (Model–Template–View), который по структуре напоминает MVC (Model–View–Controller), но с важным отличием:

  • в Django шаблон (Template) выполняет роль View из MVC,
  • а контроллером выступает сам фреймворк, обрабатывающий маршрутизацию и запросы.

Распределение ответственности в паттерне MTV:

Model (Модель):

  • Определяет логическую структуру данных и управляет доступом к базе.
  • Выступает связующим звеном между БД и представлением.

Template (Шаблон):

  • Отвечает за слой представления (UI).
  • Django использует шаблонизатор на базе обычного текста, в котором описано всё, что в итоге отображается в браузере.

View (Представление):

  • Получает данные из модели, и передаёт их в шаблон для отображения.
  • Чаще всего именно здесь пишется бизнес-логика запроса.

Контроллером в Django выступает сам фреймворк — он направляет входящий HTTP-запрос к нужному представлению, используя конфигурацию маршрутов из файла urls.py.

При разработке любого проекта на Django вы будете постоянно работать с четырьмя ключевыми компонентами:

  • моделями (models)
  • представлениями (views)
  • шаблонами (templates)
  • маршрутами (URLs)

В этой статье вы узнаете, как все эти компоненты взаимодействуют между собой, и как на их основе строится полноценное веб-приложение.

Архитектура Django

На рисунке 1.1 показано, как Django обрабатывает входящие HTTP-запросы и как управляется цикл запроса-ответа с участием основных компонентов фреймворка: urls, views, models и templates.

Жизненный цикл запроса в Django:

  1. Пользователь отправляет запрос (например, открывает страницу в браузере).
  2. Django ищет совпадение в urls.py — и перенаправляет запрос в соответствующее views.py представление.
  3. View при необходимости запрашивает данные из модели.
  4. Полученные данные передаются в шаблон, который рендерит HTML-ответ.
  5. Сформированный HTTP-ответ отправляется обратно пользователю в браузер.

Рисунок 1.1 Архитектура Django
Рисунок 1.1 Архитектура Django

Как Django обрабатывает HTTP-запросы и формирует ответы

  1. Браузер отправляет запрос по определённому URL, и веб-сервер передаёт его в Django-приложение.
  2. Django сравнивает запрошенный URL с маршрутами, указанными в urls.py, и останавливается на первом совпадении.
  3. Django выполняет представление (view), связанное с найденным маршрутом.
  4. Представление при необходимости обращается к модели, чтобы получить данные из базы данных.
  5. Модели в Django отвечают за структуру и поведение данных, и используются для выполнения запросов к БД.
  6. Представление рендерит шаблон (обычно HTML) с полученными данными и возвращает HTTP-ответ клиенту (в браузер).

К концу этой статьи вы вернётесь к этой схеме снова, в разделе "Цикл запроса/ответа (request/response cycle)", где мы разберём весь процесс ещё глубже и на практике.

Middleware — промежуточные обработчики

В Django также существуют специальные точки расширения в процессе обработки запроса — это так называемые middleware (промежуточные слои обработки).

Они встраиваются в цепочку обработки request/response и позволяют выполнять действия до и после вызова представлений (views).

Middleware намеренно не включены в диаграмму выше
для простоты восприятия архитектуры.
Однако вы познакомитесь с ними на практике во многих примерах в последующих статьях.

В статье 17 («Развёртывание в продакшн») вы научитесь создавать собственные middleware и использовать их в боевом окружении.

Мы разобрали:

  • архитектуру Django,
  • жизненный цикл запроса/ответа,
  • ключевые компоненты: модели, представления, шаблоны и маршруты.

Теперь самое время перейти к изучению новых возможностей Django 5.

Новые возможности в Django 5

Django 5.0 представляет ряд ключевых улучшений, которые вы будете использовать в примерах этой статьи и последующих статей.

Также в Django 5 были удалены устаревшие функции, а часть текущих помечены как deprecated.

Вот основные нововведения:

Фасетные фильтры в админке (Facet filters)

Теперь в административной панели можно включать фасетные фильтры. Они позволяют отображать количество объектов рядом с каждым фильтром — для наглядной фильтрации в списке объектов.

Пример использования вы найдёте в разделе «Добавление фасетных фильтров» этой статьи.

Упрощённые шаблоны для рендеринга полей форм

Теперь можно определять группы полей форм с шаблонами, в которых описаны метки, поля ввода, текст подсказки и ошибки. Это делает рендеринг форм более контролируемым и компактным.

Пример — в разделе «Шаблон комментариев» (Статья 2).

Значения по умолчанию, вычисляемые на уровне базы данных

Теперь можно задавать default‑значения, которые вычисляются не Python-кодом, а самой СУБД.

Пример — в разделе «Добавление полей с датой и временем» (в этой статье).

Генерируемые поля модели на уровне базы данных

Появился новый тип поля: Database-generated field. Значение поля автоматически вычисляется при каждом изменении модели с помощью SQL-синтаксиса GENERATED ALWAYS AS (...). Это позволяет, например, создавать вычисляемые колонки прямо в модели Django.

Новые способы задания choices в полях модели

Теперь в choices можно передавать словарь или функцию, а не только итеративные перечисления. Также больше не нужно явно обращаться к .choices для работы с Enum-полями.

Пример — в разделе «Добавление поля статуса» (в этой статье).

Асинхронность и обновления совместимости в Django 5

Django 5 также включает значительные улучшения в поддержке асинхронного кода.

Поддержка ASGI (Asynchronous Server Gateway Interface) была впервые добавлена в Django 3.0 и значительно расширена в Django 4.1 за счёт:

  • асинхронных обработчиков классов-представлений (CBV);
  • начальной реализации асинхронного ORM-интерфейса.

Что нового в Django 5 по части асинхронности:

  • Асинхронные функции добавлены в модуль аутентификации (auth);
  • Появилась поддержка асинхронных сигналов;
  • Асинхронность реализована во многих встроенных декораторах
  • (например, @login_required, @permission_required и др.).

Удалена поддержка Python 3.8 и 3.9

С выходом Django 5.0 официально прекращена поддержка Python 3.8 и 3.9. Теперь поддерживаются только Python 3.10, 3.11 и 3.12.

Полный список всех изменений в Django 5.0 смотри в релиз-нотах:

https://docs.djangoproject.com/en/5.0/releases/5.0/

Обновление существующего проекта до Django 5

Так как Django использует модель релизов, привязанную ко времени (time-based releases), в версии 5.0 нет радикальных изменений, и переход с Django 4 на 5 достаточно плавный и прямолинейный.

Быстрое обновление через django-upgrade

Если вы хотите быстро обновить существующий проект до Django 5.0, используйте утилиту django-upgrade.

Этот инструмент автоматически переписывает код проекта, применяя фиксеры (fixers) до заданной версии Django.

pip install django-upgrade
django-upgrade .

Поддерживает обновление до Django 5.0 с учётом всех устаревших конструкций.

Обновление синтаксиса Python через pyupgrade

Инструмент pyupgrade вдохновил создание django-upgrade. Он предназначен для автоматического обновления синтаксиса Python-кода до актуальной версии (например, 3.12).

pip install pyupgrade
pyupgrade --py312-plus path/to/file.py

Оба инструмента можно использовать в рамках CI/CD, или вручную для рефакторинга существующих кодовых баз.

Создание первого проекта

Ваш первый проект на Django 5 будет представлять собой приложение для ведения блога. Этот проект даст вам прочную базу для изучения возможностей и архитектуры Django.

Почему именно блог? Потому что в нём естественным образом сочетаются как базовые функции, так и продвинутые возможности, такие как:

  • управление контентом;
  • система комментариев;
  • публикация и репосты;
  • полнотекстовый поиск;
  • рекомендации похожих постов.

Проект блога будет подробно разбираться в первых трёх статьях этой подборки.

В этой статье вы:

  1. Создадите новый Django-проект;
  2. Добавите в него приложение для ведения блога;
  3. Опишете модели данных и синхронизируете их с базой данных;
  4. Настроите админ-панель Django для управления записями блога;
  5. Построите основные представления, шаблоны и маршруты (views, templates, URLs).

На рисунке 1.2 представлена схема будущих страниц блога, которые вы создадите в этой статье.

Рисунок 1.2: Схема функциональности, реализуемая в статье 1
Рисунок 1.2: Схема функциональности, реализуемая в статье 1

Страницы приложения для ведения блога

Приложение блога будет включать следующие страницы:

Страница списка постов

  • заголовок поста;
  • дата публикации;
  • имя автора;
  • краткое описание (выдержка);
  • ссылка «Читать полностью».

Эта страница будет реализована через представление post_list.

В этой статье вы научитесь создавать представления (views) на Django.

Страница одного поста (детальная)

Когда пользователь нажимает на ссылку поста в списке, он будет перенаправлен на детальную страницу записи, где будет отображаться:

  • заголовок поста;
  • дата публикации;
  • автор;
  • полный текст записи.

Теперь давайте перейдём к практике и создадим Django‑проект для блога. Django предоставляет команду, которая позволяет автоматически сгенерировать начальную структуру проекта с нужными директориями и файлами.

Создание Django-проекта

Выполните следующую команду в терминале:

django-admin startproject mysite

Эта команда создаст новый Django-проект с именем mysite и с базовой структурой директорий и конфигурацией.

⚠ Важно:

Не называй проект именами встроенных модулей Python или Django, например: django, test, site, email, чтобы избежать конфликтов при импорте.

Давай посмотрим на структуру созданного проекта

После выполнения команды:

django-admin startproject mysite

будет создана следующая структура каталогов:

Структура созданного проекта mysite
Структура созданного проекта mysite

Объяснение структуры:

  • manage.py — точка входа для управления проектом (запуск сервера, миграции и т.д.).
  • mysite/ (вложенная папка) — основной модуль проекта.
  • __init__.py — делает папку Python-модулем.
  • settings.py — все настройки проекта (базы данных, приложения, middleware).
  • urls.py — маршруты проекта.
  • asgi.py — точка входа для ASGI-серверов (асинхронность).
  • wsgi.py — точка входа для WSGI-серверов (классический запуск).

Применение начальных миграций базы данных

Каждое Django-приложение требует базу данных для хранения данных моделей. В файле settings.py присутствует настройка DATABASES, которая определяет тип и параметры подключения к БД.

По умолчанию используется SQLite

  • В settings.py уже задана конфигурация для использования SQLite3.
  • SQLite встроен в Python 3 и не требует установки.
  • Это легковесная БД, отлично подходящая для локальной разработки и тестирования.
В settings.py задана конфигурация для использования SQLite3
В settings.py задана конфигурация для использования SQLite3

Применение миграций

После создания проекта выполни команды:

cd mysite
python manage.py migrate

Эта команда:

  • Создаёт начальные таблицы в БД (например, для аутентификации, админки и сессий).
  • Применяет все миграции, определённые в установленных приложениях (INSTALLED_APPS).
Все миграции, определённые в установленных приложениях (INSTALLED_APPS)
Все миграции, определённые в установленных приложениях (INSTALLED_APPS)

Рекомендации

Для продакшена рекомендуется использовать:

  • PostgreSQL (официально рекомендован Django),
  • MySQL,
  • Oracle.

Документация по настройке БД в Django:

https://docs.djangoproject.com/en/5.0/topics/install/#database-installation

INSTALLED_APPS и миграции моделей по умолчанию

Что такое INSTALLED_APPS

В файле settings.py находится список INSTALLED_APPS. Это перечень всех Django-приложений, подключённых к вашему проекту.

Пример:

Список INSTALLED_APPS в файле settings.py
Список INSTALLED_APPS в файле settings.py

Эти приложения поставляются вместе с Django и активны в каждом новом проекте.

Что делают эти приложения

Каждое из них определяет свои модели данных (например, User, Group, Session), которые необходимо отразить в базе данных.

Миграции: что это и зачем

Миграции — это способ Django управлять структурой базы данных:

  • Создание таблиц под модели,
  • Изменения в полях моделей (добавление, удаление, переименование),
  • Переносы данных (если необходимо).

Применение стартовых миграций

Для применения миграций встроенных приложений используйте:

python manage.py migrate

Эта команда:

  • Прочитает все доступные миграции (*.py файлы в папках migrations/),
  • Применит их к текущей БД (db.sqlite3 по умолчанию),
  • Создаст таблицы и индексы.

После выполнения ты увидишь в терминале сообщения вида:

Сообщение в терминале после выполнения команды
Сообщение в терминале после выполнения команды

Запуск сервера разработки Django

Django включает встроенный отладочный сервер, который позволяет быстро запускать проект без сложной настройки продакшн-сервера. Это идеальный способ начать локальную разработку и сразу видеть изменения в браузере.

Команда для запуска сервера:

python manage.py runserver

Ожидаемый вывод в терминале:

Вывод в терминале после выполнения команды для запуска сервера
Вывод в терминале после выполнения команды для запуска сервера

Откройте браузер и перейдите по адресу:

http://127.0.0.1:8000/
Рисунок 1.3 Стартовая страница django в браузере.
Рисунок 1.3 Стартовая страница django в браузере.

Страница запуска Django и логирование запросов

Что означает стартовая страница Django?

Когда вы откроете адрес страницы http://127.0.0.1:8000/ после запуска runserver, вы увидите стандартную страницу Django — это подтверждение того, что проект успешно запущен.

📋 Что пишет консоль?

Запись в консоли при заходе через браузер на базовую страницу Django
Запись в консоли при заходе через браузер на базовую страницу Django

Каждый HTTP-запрос, поступающий на сервер, отображается в терминале. Это удобно для отладки, особенно если вы хотите отслеживать, какие страницы запрашиваются и какие ошибки происходят.

Запуск с кастомными параметрами

Вы можете указать другой хост, порт или даже другой файл настроек:

python manage.py runserver 127.0.0.1:8001 --settings=mysite.settings

Полезно при работе с несколькими окружениями (например, dev / staging / prod), для каждого из которых можно создать отдельный файл настроек (settings_dev.py, settings_prod.py и т. д.).

Важно: сервер не для продакшена

Встроенный сервер Django предназначен только для разработки.

Для запуска в production-среде следует использовать:

  • WSGI-серверы: Gunicorn, uWSGI, mod_wsgi (Apache)
  • ASGI-серверы: Uvicorn, Daphne

Подробнее:

Официальная документация Django — Deployment

В статье 17 "Выход в продакшн" вы научитесь правильно настраивать продакшн-окружение для Django 5.

Основные настройки проекта Django (settings.py)

При создании проекта Django автоматически формирует файл settings.py, в котором хранится конфигурация проекта. Ниже разберём ключевые параметры:

DEBUG

DEBUG = True
  • Включает режим отладки.
  • Никогда не включай DEBUG=True в продакшне — это может раскрыть конфиденциальные данные (секреты, пути, трейсбэки).
  • В проде всегда устанавливай:
DEBUG = False

ALLOWED_HOSTS

ALLOWED_HOSTS = ['yourdomain.com', '127.0.0.1']
  • Список доменов/хостов, с которых разрешено обращаться к проекту при DEBUG = False.
  • В режиме разработки (DEBUG=True) этот список игнорируется.

INSTALLED_APPS

Список активных приложений проекта:

Список активных приложений проекта
Список активных приложений проекта

Вы будете добавлять сюда свои приложения, например: 'blog', 'users', 'api'.

MIDDLEWARE

"хуки", которые обрабатывают запросы/ответы между клиентом и view-функцией.
"хуки", которые обрабатывают запросы/ответы между клиентом и view-функцией.
  • Промежуточный слой — "хуки", которые обрабатывают запросы/ответы между клиентом и view-функцией.
  • Позволяет внедрять кэш, защиту от XSS, CSRF и др.

ROOT_URLCONF

ROOT_URLCONF = 'mysite.urls'
  • Указывает на модуль, где находится главный файл маршрутизации (urls.py).
  • Django начинает поиск URL-путей с этого файла.

DATABASES

Настройки подключения к БД
Настройки подключения к БД
  • Здесь вы настраиваете подключение к БД.
  • Для продакшена рекомендуются: PostgreSQL, MySQL, Oracle.

LANGUAGE_CODE

LANGUAGE_CODE = 'en-us'

Язык по умолчанию для проекта.

Для русского языка:

LANGUAGE_CODE = 'ru-ru'

USE_TZ

USE_TZ = True
  • Включает поддержку часовых поясов.
  • Для корректной работы с datetime, особенно в продакшене и API, оставьте включённой.

Проекты и приложения в Django

На протяжении всего обучения вы будете регулярно сталкиваться с понятиями проект и приложение.

В контексте Django эти термины имеют чёткое разделение:

  • Проект Django — это вся установка Django, включающая:
  • файл settings.py с настройками,
  • маршруты верхнего уровня (urls.py),
  • конфигурацию баз данных, приложений, языка и прочее.
  • Приложение Django — это самостоятельный модуль, содержащий:
  • модели данных (models.py),
  • представления (views.py),
  • шаблоны (templates/),
  • маршруты (urls.py),
  • формы, сериализаторы и т.д.

Принцип модульности

Приложения — это блоки функциональности, которые можно переиспользовать в других проектах.

Например, у тебя может быть:

  • Приложение blog — для публикации постов.
  • Приложение forum — для общения пользователей.
  • Приложение wiki — для хранения статей.

Один проект может включать любое количество приложений, и каждое из них может быть независимым.

Аналогия

  • Проект = сайт целиком.
  • Приложение = логически обособленный функциональный модуль (блог, магазин, API, CMS и т.п.).

На рисунке 1.4 показана стандартная структура проекта на Django:

Рисунок 1.4 Стандартная структура проекта на Django
Рисунок 1.4 Стандартная структура проекта на Django

Корень проекта содержит файл manage.py, директорию с настройками проекта, и одну или несколько директорий приложений.

Файловая структура корневой папки проекта на Django
Файловая структура корневой папки проекта на Django

Создание приложения

Cоздадим первое Django-приложение. Мы будем строить блог с нуля.

Команда для создания приложения:

# не забываем активировать виртуальное окружение проекта my_env
source my_env/bin/activate
# переходим в папку проекта Django - mysite, где лежит файл manage.py
cd mysite
# команда для создания приложения blog
python manage.py startapp blog

Запустите её из корня проекта, рядом с файлом manage.py

Django создаст следующую структуру:

Файловая структура приложения blog
Файловая структура приложения blog

Что делает каждый файл:

  • __init__.py: Превращает директорию blog в Python-модуль.
  • admin.py: Регистрирует модели для отображения в админке (опционально).
  • apps.py: Конфигурация приложения, например его имя или сигналы.
  • migrations/: Здесь Django будет сохранять файлы миграций, отражающих изменения в моделях.
  • models.py: Определяются модели данных (ORM). Обязательный файл даже если он пока пуст.
  • tests.py: Место для написания unit-тестов и тестирования логики приложения.
  • views.py: Основная бизнес-логика — обработка HTTP-запросов и возврат ответов.

Посмотрите как выглядит файловая структура проекта Django с приложением blog:

Файловая структура всего проекта с приложением blog
Файловая структура всего проекта с приложением blog

Теперь, когда структура приложения blog готова, мы можем перейти к следующему шагу — созданию моделей данных для блога.

Создание моделей данных для блога

Напомним: в Python объект — это комбинация данных и методов, а класс —это шаблон, по которому создаются объекты.

При создании класса вы описываете структуру данных и их поведение.

Что такое модель в Django

В Django модель (model) — это описание структуры данных и логики работы с ними.

Модель представляет собой Python-класс, который наследуется от django.db.models.Model.

Каждый атрибут класса становится полем таблицы в базе данных.

Django автоматически:

  • создаёт таблицу под каждую модель;
  • предоставляет ORM-интерфейс для запросов (.objects.all(), .filter(), .get() и т. д.);
  • отслеживает изменения в моделях через механизм миграций.

Процесс создания моделей:

  1. Определим модели в файле blog/models.py.
  2. Сгенерируем миграции с помощью команды makemigrations.
  3. Применим миграции с помощью команды migrate, чтобы Django создал таблицы в базе данных.

Таким образом, каждый класс-модель, определённый в models.py, будет соответствовать отдельной таблице в базе. А каждое поле — это столбец в этой таблице.

Создание модели Post

Мы начнём реализацию блога с создания модели Post, которая будет представлять отдельную статью в блоге и сохраняться в базе данных.

Открой файл blog/models.py и добавь следующий код:

Код модели Post оформленный в соотвествии с требованиями PEP8.
Код модели Post оформленный в соотвествии с требованиями PEP8.

Рекомендую печатать код самостоятельно, а не копировать с репозитория GitHub, чтобы у вас формировалась мышечная память и вы могли писать код без интернета на бумажке, это часто просят сделать на собеседованиях.

Разбор каждого поля:

  • title — CharField(max_length=250)
  • Переводится в SQL как VARCHAR(250)
  • Используется для хранения заголовка поста.
  • slug — SlugField(max_length=250)
  • Это "читаемая часть URL", которая может быть частью адреса страницы.
  • Например: django-reinhardt-legend-jazz для заголовка “Django Reinhardt: A legend of Jazz”
  • Позволяет формировать SEO-дружественные адреса.
  • body — TextField()
  • SQL: TEXT
  • Основной контент статьи — без ограничения длины.
  • __str__ метод
  • Возвращает строковое представление объекта.
  • Django использует его в админке, shell и прочих местах, где нужно отобразить объект как строку.
Рисунок 1.5: Связь между моделью Post и таблицей базы данных.
Рисунок 1.5: Связь между моделью Post и таблицей базы данных.

На рисунке 1.5 показано, как Django автоматически отображает определение модели на структуру таблицы базы данных:

Левая часть: модель Django (Post)

  • id — BigAutoField
  • Автоматически создаётся Django, если явно не указано другое поле primary_key. Используется как уникальный идентификатор записи.
  • title — CharField(max_length=250)
  • Строковое поле с ограничением длины. Соответствует VARCHAR(250) в SQL.
  • slug — SlugField(max_length=250)
  • Строка, пригодная для использования в URL. Соответствует VARCHAR(250) в SQL.
  • body — TextField()
  • Длинный текст. В SQL — TEXT, используется для основного содержимого поста.

Правая часть: таблица в базе данных (blog_post)

Когда вы запускаете команду python manage.py makemigrations и затем migrate, Django создаёт таблицу blog_post со следующими полями:

  • id — integer, Primary Key
  • title — varchar(250)
  • slug — varchar(250)
  • body — text

Первичный ключ по умолчанию в Django

По умолчанию, Django автоматически добавляет первичный ключ (primary key) в каждую модель, если вы его явно не указали.

  • Тип этого поля определяется настройкой DEFAULT_AUTO_FIELD.
  • Значение по умолчанию при создании приложения через startapp — это BigAutoField.

BigAutoField — это 64-битное целое число, которое:

  • автоинкрементируется (увеличивается при каждой новой записи),
  • используется как уникальный идентификатор строки в таблице,
  • представляет собой поле id в базе данных.

Добавление полей даты и времени

Теперь мы продолжим расширение модели Post, добавив в неё поля для хранения даты и времени.

Каждый пост в блоге должен иметь дату и время публикации, поэтому нам нужно поле, которое будет хранить эти данные. Кроме того, мы хотим фиксировать дату и время создания записи, а также дату и время её последнего обновления.

Чтобы реализовать это, откройте файл models.py в приложении blog и отредактируй его следующим образом:

В код модели Post добавили поле publish для хранения даты и времени публикации поста.
В код модели Post добавили поле publish для хранения даты и времени публикации поста.

Мы добавили поле publish в модель Post. Это поле является экземпляром класса DateTimeField, что означает, что в базе данных оно будет представлено как столбец типа DATETIME.

Мы используем это поле для хранения даты и времени публикации поста.

В качестве значения по умолчанию для publish используется функция timezone.now.

Это важно: timezone.now — это таймзонно-осознанный аналог стандартной функции datetime.now() из Python.

В отличие от datetime.now(), которая возвращает "наивное" значение времени (без привязки к часовому поясу), timezone.now() возвращает объект datetime, привязанный к таймзоне, указанной в настройке TIME_ZONE в settings.py, и учитывает параметр USE_TZ.

Чтобы использовать timezone.now, мы импортировали модуль django.utils.timezone. Это позволяет Django корректно работать с временными зонами, например, при отображении времени в админке или при использовании фильтров по дате.

Использование timezone.now — рекомендуемая best practice в Django-проектах, где USE_TZ = True (а это сейчас используется по умолчанию).

Альтернативный способ задания значений по умолчанию в Django 5

Другой способ задать значения по умолчанию для полей модели Post — это использование вычисляемых значений на уровне базы данных (database-computed default values).

Начиная с Django 5, вы можете использовать функции самой базы данных (например, NOW() в PostgreSQL или MySQL), чтобы задавать дефолтные значения.

Это особенно полезно, когда важно, чтобы значение устанавливалось не в Python-коде, а в самой СУБД — например, для точной синхронизации по времени или при массовых вставках данных.

Задали значение по умолчанию для поля publish модели Post — это пример использования вычисляемых значений на уровне базы данных, которые появились в Django 5.
Задали значение по умолчанию для поля publish модели Post — это пример использования вычисляемых значений на уровне базы данных, которые появились в Django 5.

Важно:

  • db_default=Now() требует PostgreSQL 12+ или другую СУБД, поддерживающую функцию NOW() или аналог.
  • db_default работает только на уровне базы данных, то есть при создании записи напрямую через SQL или через .save(force_insert=True) без указания publish, значение будет установлено на стороне БД.
  • При использовании Model.objects.create(...) через Django ORM publish не будет автоматически подставлено в объект Python — значение появится только после .refresh_from_db() или save() с перезагрузкой.

Чтобы использовать значения по умолчанию, генерируемые на уровне базы данных, в Django 5 необходимо использовать атрибут db_default вместо default.

В нашем примере:

При использовании вычисляемых полей в базе данных в Django 5 необходимо использовать атрибут db_default вместо default.
При использовании вычисляемых полей в базе данных в Django 5 необходимо использовать атрибут db_default вместо default.

Мы указываем, что значение по умолчанию для поля publish будет задаваться функцией NOW() непосредственно на стороне СУБД (например, PostgreSQL). Это эквивалентно default=timezone.now, но отличие в следующем:

  • default=timezone.now — дата и время задаются в момент вызова Python-кода, то есть до попадания данных в базу;
  • db_default=Now() — значение вычисляется самой базой данных в момент вставки записи.

Такой подход позволяет делегировать инициализацию значений базе данных, что особенно полезно:

  • при использовании чистого SQL;
  • в случаях, когда Django ORM не управляет созданием записей напрямую;
  • при необходимости обеспечить согласованность времени в одной временной зоне.

Дополнительная информация:

Продолжим использовать предыдущую версию поля publish:

Будем использовать версию models.py, где publish задается через default=timezone.now
Будем использовать версию models.py, где publish задается через default=timezone.now

Откройте файл models.py в приложении blog и добавьте следующие строки:

Добавили в модели данных Post поля: created и updated.
Добавили в модели данных Post поля: created и updated.

Пояснение к новым полям:

  • publish — дата и время публикации поста. Значение по умолчанию берётся с помощью timezone.now, что даёт timezone-aware объект (с учётом часового пояса). Это предпочтительно в проектах с международными пользователями.
  • created — автоматически устанавливается в момент первого создания объекта. Полезно для аудита и отслеживания, когда пост был создан.
  • updated — обновляется каждый раз при сохранении объекта. Используется для отслеживания изменений (например, в админке или при отображении "Последние изменения").
Использование полей auto_now_add и auto_now в моделях Django — это наилучшая практика для отслеживания времени создания и последнего изменения объектов в базе данных.

Расшифровка:

  • auto_now_add=True
  • Устанавливает значение поля только один раз при создании объекта.
  • Пример: created = models.DateTimeField(auto_now_add=True)
  • Используется для логирования или аудита — фиксирует момент создания записи.
  • auto_now=True
  • Обновляет значение поля каждый раз при вызове save().
  • Пример: updated = models.DateTimeField(auto_now=True)
  • Полезно для хранения даты последнего изменения — например, чтобы отобразить пользователю, когда пост в блоге редактировался в последний раз.

Лучшие практики:

  • Такие поля не требуют ручной установки значений и полностью автоматизируют ведение временной метаинформации.
  • Их не нужно указывать в формах — они заполняются на уровне модели.
  • Особенно актуально при аудите изменений, упрощённой отладке или отображении в UI ("Создан", "Обновлён").

Определение порядка сортировки в модели по умолчанию

На практике блоги, ленты новостей и любые временные публикации обычно отображаются в обратном хронологическом порядке — т.е. сначала идут самые новые записи, а затем — более старые.

Чтобы достичь такого поведения в модели Django, мы можем задать сортировку по умолчанию (default ordering), которая будет применяться в каждом запросе к базе данных, если вы не указали явный порядок вручную (например, через .order_by()).

Для этого в модели Post необходимо определить вложенный класс Meta и в нём указать атрибут ordering.

Добавли в код модели данных Post вложенный класс Meta и в нём указали атрибут ordering.
Добавли в код модели данных Post вложенный класс Meta и в нём указали атрибут ordering.

Что делает ordering = ['-publish']

  • Это атрибут мета-класса Meta, который сообщает Django, что при выборке объектов модели нужно отсортировать их по полю publish в порядке убывания (от самого нового к старому).
  • Знак минус - перед названием поля — это сокращение от SQL ORDER BY publish DESC.
  • Таким образом, при вызове Post.objects.all() ты автоматически получишь записи, отсортированные от новых к старым.

Добавление индекса в базу данных

Чтобы повысить производительность запросов, особенно тех, что фильтруют или сортируют данные по полю publish, мы добавим индекс на это поле.

Это особенно важно, поскольку мы уже задали сортировку по умолчанию по publish. А значит, подавляющее большинство запросов будет использовать это поле — и база данных сможет быстрее возвращать отсортированные результаты благодаря индексу.

Что нужно изменить

Откройте файл models.py и обновите модель Post следующим образом:

Во вложенном классе Meta добавили индекс indexes.
Во вложенном классе Meta добавили индекс indexes.

Пояснение: как работает indexes

Что делает indexes = [...]:

  • Внутри класса Meta мы определяем список индексов модели.
  • Каждый индекс — это объект models.Index(...).
  • В параметре fields указываем поле, по которому строится индекс.
  • Префикс - перед полем означает, что индекс создаётся по убыванию значений — это может быть полезно, если по этому же полю идёт сортировка в убывающем порядке (order_by('-publish')).
  • Этот индекс включается в миграции, т.е. он будет создан в базе при выполнении makemigrations и migrate.

Важно учитывать:

  • Индекс упрощает поиск и сортировку. Если у тебя в базе десятки или сотни тысяч записей — это критически важно.
  • В MySQL индекс по убыванию (-field) обрабатывается как обычный индекс, потому что MySQL на уровне ядра не поддерживает "directional" индексы (в отличие от PostgreSQL).
  • Ты можешь создать составные индексы:
models.Index(fields=['-publish', 'title'])

— это удобно, если вы часто сортируете или фильтруете по нескольким полям.

Дополнительная информация по индексам в Django

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

Перейди по следующей ссылке, чтобы изучить:

  • как добавлять одиночные и составные индексы,
  • как использовать функции БД в индексах,
  • как задать уникальные, условные и полнотекстовые индексы,
  • какие ограничения есть у разных баз данных (например, MySQL и SQLite).

Документация Django 5:

https://docs.djangoproject.com/en/5.0/ref/models/indexes/

Рекомендация от senior-разработчика

Используйте индексы только там, где они реально ускоряют запросы. Добавление избыточных индексов замедляет INSERT/UPDATE, а также увеличивает размер базы. Следите за тем, какие поля чаще всего используются в filter(), order_by() и select_related() — и индексируйте именно их.

Активация приложения в Django

Чтобы Django начал отслеживать твоё новое приложение blog и мог создать таблицы базы данных для его моделей, необходимо явно подключить приложение в настройках проекта.

Откройте файл settings.py и найдите список INSTALLED_APPS. Это список всех активных приложений Django в проекте.

Добавьте в него ссылку на конфигурационный класс вашего приложения:

Подключили приложение blog в файле settings.py через класс конфигурации приложения.
Подключили приложение blog в файле settings.py через класс конфигурации приложения.

Почему указывается blog.apps.BlogConfig, а не просто 'blog'?

Django позволяет регистрировать приложение либо просто по его имени ('blog'), либо более формально — через класс конфигурации приложения, например:

Класс конфигурации приложения blog.
Класс конфигурации приложения blog.

Указание blog.apps.BlogConfig более гибкое:

  • позволяет задать настройки по умолчанию (например, тип id);
  • задаёт читаемое имя в админке;
  • может быть использовано для подключения сигналов или другого стартап-кода.

После активации:

  • Django будет видеть модели из blog/models.py;
  • можно будет выполнять makemigrations и migrate;
  • приложение появится в админке, если модели будут зарегистрированы в файле blog/admins.py.

Добавление поля status для управления статусом поста: опубликовано/черновик

Одна из частых задач при разработке блога — реализовать возможность сохранять пост как черновик, чтобы автор мог вернуться к нему позже. Мы добавим к модели Post специальное поле status, которое будет обозначать текущее состояние публикации: черновик или опубликовано.

🛠 Что мы делаем:

  1. Внутри модели Post определяем вложенный перечислимый класс Status, унаследованный от models.TextChoices.
  2. Добавляем поле status в модель, использующее это перечисление.

Вот как будет выглядеть модель Post:

В модели Post пределяем вложенный перечислимый класс Status и Добавляем поле status.
В модели Post пределяем вложенный перечислимый класс Status и Добавляем поле status.

Как это работает

  • models.TextChoices — это Django-обёртка над enum.Enum, которая позволяет легко определить: допустимые значения (value, например, 'DF'), отображаемые названия (label, например, 'Черновик')
  • В результате:
  • В админке и формах будет выпадающий список с понятными названиями.
  • Django будет валидировать значения status, не позволяя записать в поле что угодно.

Дополнительные ссылки

Что мы получаем от перечисления Status

После того как мы определили class Status(models.TextChoices) внутри модели Post, Django предоставляет нам расширенный доступ к выборкам и атрибутам:

Доступные атрибуты перечисления:

Post.Status.choices возвращает список кортежей (value, label), пригодный для поля choices.

Post.Status.names возвращает список строк с именами (например, 'DRAFT', 'PUBLISHED')

Post.Status.labels возвращает список человекочитаемых названий (например, 'Черновик)

Post.Status.values возвращает список внутренних значений (например, 'DF', 'PB')

Поле status в модели

-31
  • Поле status хранит одно из двух значений:
  • 'DF' — Черновик (Draft)
  • 'PB' — Опубликовано (Published)
  • Благодаря параметру choices Django автоматически:
  • валидирует значения при сохранении модели;
  • генерирует выпадающий список в формах (в том числе в админке);
  • делает код самодокументируемым.
Хорошей практикой считается определять варианты значений поля (choices) внутри класса модели, используя перечисления (enumeration types). Это делает код более читаемым, самодокументируемым и удобным для поддержки.

Например, вы можете импортировать модель Post и в любом месте проекта использовать ссылку Post.Status.DRAFT — это гораздо нагляднее и безопаснее, чем напрямую писать 'DF'.

Такой подход:

  • Исключает "магические строки" в коде.
  • Упрощает автодополнение и рефакторинг в редакторах.
  • Помогает избежать опечаток при использовании статусов вручную.

📌 Вопросы, которые часто задают на собеседованиях:

  1. 👉 Почему лучше использовать models.TextChoices для определения вариантов choices, а не обычный список кортежей?
  2. 👉 В чём разница между Status.DRAFT, Status.DRAFT.label и Status.DRAFT.value?
  3. 👉 Какие преимущества дают перечисления (Enum) в Django по сравнению с обычными строками?
  4. 👉 Можно ли использовать Enum классы вне моделей, например, в формах или представлениях?

Давайте посмотрим, как работать с вариантами значений поля status (choices) на практике.

Работа со статусами постов через Django shell

Посмотрим, как можно взаимодействовать с перечислением (choices) статусов, которое мы определили в модели Post.

Шаг 1. Запуск Python-интерпретатора Django

Выполни команду в терминале из корня проекта:

python manage.py shell

Это откроет интерактивную оболочку Python с доступом к окружению Django проекта.

Шаг 2. Импорт модели и вызов статусов

В интерактивной оболочке введите:

from blog.models import Post
Post.Status.choices

Результат:

-32
[('DF', 'Черновик'), ('PB', 'Опубликовано')]


Это список кортежей, в которых:

  • первый элемент ('DF', 'PB') — значение, сохраняемое в базе данных;
  • второй элемент ('Черновик', 'Опубликовано') — человекочитаемая метка (label), которая будет отображаться в формах, интерфейсах и админке.

Такой подход гарантирует:

  • контроль над допустимыми значениями поля status;
  • удобную интеграцию с формами;
  • автообновление меток без необходимости менять бизнес-логику.

Как работать с перечислением Status в модели Post

После запуска Django shell (python manage.py shell) вы можете получить разные представления перечисления Status, определённого в модели Post.

Получить читаемые метки (labels)

В интерактивной оболочке введите:

Post.Status.labels

Результат:

['Черновик', 'Опубликовано']

Здесь возвращаются человекочитаемые названия (label) для интерфейсов (админка, формы), определённые как второй элемент кортежа в choices.

Получить значения для базы данных (values)

В интерактивной оболочке введите:

Post.Status.values

Результат:

['DF', 'PB']

Это те значения, которые фактически сохраняются в базе данных в поле status. Они соответствуют первым элементам кортежей в choices.

Получить имена перечислений (names)

В интерактивной оболочке введите:

Post.Status.names

Результат:

['DRAFT', 'PUBLISHED']

Это идентификаторы членов перечисления, используемые в коде. Например, Post.Status.DRAFT.name == "DRAFT".

Пример использования в коде

-33

Такой подход даёт тебе:

  • автодополнение в IDE,
  • защиту от опечаток (вместо строк),
  • единый источник истины.

Доступ к конкретному элементу перечисления (enum)

В Django, если вы используете models.TextChoices в модели (например, Post.Status), вы можете обращаться к конкретным членам перечисления напрямую, как к атрибутам класса:

В интерактивной оболочке введите:

print(Post.Status.PUBLISHED)

Результат:

PB

Вы также можете получить его имя (name) и значение (value).

В интерактивной оболочке введите:

Post.Status.PUBLISHED.name

Результат:

'PUBLISHED'

В интерактивной оболочке введите:

Post.Status.PUBLISHED.value

Результат:

'PB'

Это особенно удобно при написании условий и логики в коде, где важно:

  • использовать константы, а не "магические строки";
  • избежать ошибок из-за опечаток (Post.Status.PUBLISHED, а не 'PB');
  • обеспечить авто-дополнение и проверку типов в IDE.

Пример использования в коде

-34

Добавление связи «многие к одному»
(Many-to-One)

Каждый пост в блоге обязательно должен иметь автора. Мы реализуем связь между пользователями и постами, чтобы Django знал, какой пользователь создал какой пост.

Django предоставляет встроенную систему аутентификации, которая включает модель пользователя — User.

Модель User размещена в пакете django.contrib.auth.

Чтобы связать пост с пользователем, мы будем использовать настройку AUTH_USER_MODEL, которая по умолчанию указывает на auth.User. Это позволяет использовать как стандартную модель пользователя, так и кастомную.

from django.conf import settings

author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE,related_name='blog_posts')

-35

Что нового и почему это важно?

  • author = models.ForeignKey(...) — создаёт связь между постом и пользователем. Это означает, что:
    Один пользователь может создать
    много постов;
    Каждый пост
    принадлежит одному пользователю;
    Это типичная
    связь "многие к одному" в SQL.
  • settings.AUTH_USER_MODEL — используется вместо прямого импорта модели User. Это позволяет в будущем заменить модель пользователя на кастомную без изменений в логике моделей.
  • on_delete=models.CASCADE — поведение при удалении пользователя:
    Все посты, связанные с этим пользователем, будут автоматически удалены;
    Это SQL-стандарт (аналог ON DELETE CASCADE в PostgreSQL, SQLite и др.).
  • related_name='blog_posts' — позволяет обращаться к постам из модели пользователя:
user.blog_posts.all() # Получить все посты, созданные пользователем
Рисунок 1.6 Связь модели Post Django и таблицы blog_post базы данных.
Рисунок 1.6 Связь модели Post Django и таблицы blog_post базы данных.

Полезные ссылки:

Создание и применение миграций

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

🔁 Что делает команда migrate?

Команда:

python manage.py migrate

применяет все миграции, определённые для приложений, указанных в INSTALLED_APPS. Это синхронизирует структуру БД с текущим состоянием моделей.

Шаг 1. Создание начальной миграции

Выполните команду из корня проекта где находится файл manage.py:

python manage.py makemigrations blog

Результат:

-37

Что произошло:

  • Django создал файл 0001_initial.py внутри каталога blog/migrations/.
  • Этот файл содержит SQL-инструкции для создания таблицы blog_post и индексов.

Просмотр SQL-кода миграции

Чтобы посмотреть SQL, который Django выполнит в БД, введите:

python manage.py sqlmigrate blog 0001

Результат:

-38

Особенности и детали

  • Django создаёт таблицу с названием blog_post, объединяя имя приложения и имя модели в нижнем регистре.
  • Поле id создаётся автоматически как PRIMARY KEY AUTOINCREMENT.
  • Можно переопределить имя таблицы с помощью Meta.db_table = "...".
  • Поля SlugField и ForeignKey автоматически создают индексы.
  • Индекс на publish создан нами вручную (в Meta.indexes).

Шаг 2. Применение миграций

Чтобы синхронизировать БД с миграциями, выполните команду:

python manage.py migrate

Результат:

-39

Теперь таблица blog_post и индексы реально созданы в БД.

Повторное изменение моделей

Если вы:

  • Добавили / удалили поле в модели,
  • Переименовали его,
  • Создали новую модель.

Тогда снова нужно будет:

1. Сгенерировать миграцию:

python manage.py makemigrations blog

2. Применить миграцию:

python manage.py migrate

Вывод

Система миграций в Django — мощный инструмент, который позволяет:

  • автоматически отслеживать изменения в моделях;
  • безопасно обновлять схему БД без потери данных;
  • документировать и версионировать изменения.

🧠 Часто задаваемые вопросы на собеседованиях по теме миграций и моделей Django:

🔹 Основы моделей и миграций

  1. Что делает команда makemigrations в Django?
  2. Чем отличается makemigrations от migrate?
  3. Где хранятся миграции в проекте Django?
  4. Что произойдёт, если изменить модель, но не выполнить makemigrations и migrate?
  5. Можно ли отменить миграцию? Как?
  6. Как Django определяет имя таблицы в базе данных по имени модели?
  7. Что такое Meta.db_table и зачем он нужен?

🔹 Индексы и производительность

  1. Какие поля Django индексирует автоматически?
  2. Как вручную создать индекс для поля модели?
  3. Как проверить, какие индексы созданы в базе данных?
  4. Можно ли создать составной индекс (multi-field index) в Django? Приведите пример.
  5. Какая разница между ordering и index в Meta?

🔹 Сложные связи и типы полей

  1. Как реализовать отношение "один ко многим" в Django?
  2. Зачем использовать settings.AUTH_USER_MODEL вместо User напрямую?
  3. Чем отличаются параметры on_delete=models.CASCADE и SET_NULL?
  4. Что делает параметр related_name в ForeignKey?
  5. Может ли один и тот же пользователь быть автором и редактором в разных отношениях? Как это реализовать?

🔹 Дата и время в моделях

  1. Что делает auto_now_add и чем он отличается от auto_now?
  2. Можно ли использовать default=timezone.now и db_default=Now() одновременно?
  3. Когда и почему стоит использовать db_default вместо default?

🔹 Миграции уровня senior

  1. Что такое миграционный конфликт и как его решать?
  2. Можно ли объединить несколько миграций в одну? Как это сделать?
  3. Что делает RunPython в миграциях? Примеры применения.
  4. Как выполнять data-модифицирующие миграции без потери данных?
  5. Какие best practices при написании кастомных операций в миграциях?

🔹 Работа с БД на уровне senior+

  1. Как влияет использование unique=True на производительность и структуру БД?
  2. Как задать дефолтное значение, вычисляемое на уровне базы (например, NOW())?
  3. Какие типы индексов поддерживаются в PostgreSQL и как их использовать из Django?
  4. Какие особенности и ограничения индексов в MySQL (например, невозможность desc-индексов)?
  5. Что произойдёт при смене поля CharField на TextField в модели с существующими миграциями?

Создание интерфейса администрирования моделей приложения blog


Теперь, когда модель Post синхронизирована с базой данных, мы можем создать простой интерфейс администрирования для управления записями в блоге.

Django поставляется со встроенным интерфейсом администрирования, который очень полезен для редактирования контента.

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

Вы можете использовать его "из коробки", настроив, как вы хотите, чтобы на нем отображались ваши модели.

Приложение django.contrib.admin уже включено в параметр INSTALLED_APPS, поэтому вам не нужно его добавлять.

Создание суперпользователя

Для управления административной частью веб приложения blog необходимо создать суперпользователя.

Для этого запустите команду:

python manage.py createsuperuser

Результат:

-40

Как видно на скриншоте при создании суперпользователя необходимо задать его имя, адрес электронной почты, пароль и подтверждение пароля.

Мы только что создали суперпользователя - пользователя-администратора с наивысшими правами доступа.

Страница администрирования Django

Запустите сервер разработки с помощью команды:

python manage.py runserver

В адресной строке веб браузера введите адрес:

http://127.0.0.1:8000/admin/

Результат:

Откроется страница входа в систему администрирования Django, как показано на рисунке 1.7:

Рисунок 1.7 Страница входа в систему администрирования веб приложения Django
Рисунок 1.7 Страница входа в систему администрирования веб приложения Django

Войдите в систему, используя учетные данные пользователя, которого вы создали на предыдущем шаге.

Вы увидите главную страницу системы администрирования Django, как показано на рисунке 1.8:

Рисунок 1.8 Главная страница системы администрирования Django.
Рисунок 1.8 Главная страница системы администрирования Django.

Модели групп и пользователей, которые вы видете на рисунке 1.8, являются частью платформы аутентификации Django, расположенной в django.contrib.auth.

Если вы нажмете на "Пользователи", вы увидите пользователя, которого вы создали ранее:

-43

Добавление модели Post в систему администрирования Django

Давайте добавим модель Post приложения blog в систему администрирования Django.

Для этого отредактируйте файл admin.py приложения blog:

blog/admin.py

Добавьте следующие строки кода в admin.py:

from django.contrib import admin
from .models import Post
admin.site.register(Post)
-44

Теперь обновите страницу: http://127.0.0.1:8000/admin/

Результат:

В админке сайта появилась модель Post
В админке сайта появилась модель Post

Перейдите на страницу добавления поста:

http://127.0.0.1:8000/admin/blog/post/add/

Результат:

Административный интерфейс создания поста у модели Post.
Административный интерфейс создания поста у модели Post.

Посмотрите на скриншоты и обратите внимание что подписи для полей интефейса модели Post на англйском языке - нужно перевести все на русский язык.

Русификация интерфейса модели Post в админке Django

1. Добавьте verbose_name и verbose_name_plural в Meta модели Post.

-47

2. Добавьте verbose_name и help_text для полей модели. Это улучшит отображение в админке и подскажет администратору, что и куда вводить.

-48
-49

Обновите страницу добавления поста:

http://127.0.0.1:8000/admin/blog/post/add/

Результат:

-50

После добавления в код модели Post:
verbose_name, verbose_name_plural в Meta;
verbose_name, help_text для полей модели Post;
- интерфейс переведен на русский язык и к полям добавлены пояснения там где это необходимо.

Осталось перевести название приложения BLOG на Блог.

Чтобы изменить название приложения с BLOG на БЛОГ в интерфейсе Django admin, тебе нужно задать человеко-понятное имя приложения через verbose_name в файле конфигурации приложения:

blog/apps.py

Шаг 1. Открой файл blog/apps.py

-51

Шаг 2. Убедись, что BlogConfig используется в INSTALLED_APPS:

Обновите страницу добавления поста:

http://127.0.0.1:8000/admin/blog/post/add/

Результат:

После этого в Django admin вместо BLOG вы увидите БЛОГ как название приложения.

-52

Создадим первый пост - заполните все поля текстом на ваше усмотрение.

-53

Нажмите кнопку СОХРАНИТЬ.

Результат:

-54

❗ Примечание

Если вы будете использовать локализацию (LANGUAGE_CODE = 'ru'), и при этом у вас активированы .po-файлы перевода, то verbose_name можно тоже передавать через gettext_lazy, вот так:

-55

Это будет полезно, если вы делаете мультиязычное приложение.

Кастомизация списка отображения постов

Сейчас в списке постов отоображается только название поста. Для полноценного администрирования постов в блоге этого явно не достаточно. Необходимо добавить столбцы: Автор, Дата публикации, Статус.

Для этого отредактируйте файл
blog/admin.py - как показано на скриншоте:

-56

Обновите страницу списка постов:

http://127.0.0.1:8000/admin/blog/post/

Результат:

-57

Чтобы нам было удобно фильтровать список постов по разным полезным признакам - добавим фильтр.

Для этого отредактируйте файл blog/admin.py - как показано на скриншоте:

-58

Обновите страницу списка постов:

http://127.0.0.1:8000/admin/blog/post/

Результат:

-59

Фильтры добавили. Теперь добавим сверху строку поиска, чтобы искать посты по заголовку и содержанию поста.

Для этого отредактируйте файл blog/admin.py - как показано на скриншоте:

-60

Обновите страницу списка постов:

http://127.0.0.1:8000/admin/blog/post/

Результат:

-61

Добавили строку поиска над списком постов.

Заполнять slag вручную - это вчерашний век. Давайте настроим автозаполнение slag по тексту заголовка поста транслитом на латинице.

Для этого отредактируйте файл blog/admin.py - как показано на скриншоте:

-62

Обновите страницу добавления поста:

http://127.0.0.1:8000/admin/blog/post/add/

Результат:

-63

Автозаполнение слаг по заголовку поста добавили.

На популярной блог платформе может быть много блогеров и вручную выбирать автора поста это трудозатратная задача. Давайте добавим поиск автора по ключевому слогу или слову.

Для этого отредактируйте файл blog/admin.py - как показано на скриншоте:

-64

Обновите страницу добавления поста:

http://127.0.0.1:8000/admin/blog/post/add/

Результат:

-65

Нажмите на значек лупы.

Результат:

-66

Напишите в строке поиска первые три буквы пользователя или имя пользователя целиком и кликните по нему мышкой.

Результат:

-67

Вместо имени автора - его id. Давайте это исправим. Неудобно прыгать со страницы на страницу чтобы выбрать автора. Давайте сделаем так, чтобы строка поиска появилась в поле выбора автора и при выборе автора его имя автоматом бы подставлялось в поле автор.

Для этого отредактируйте файл blog/admin.py - как показано на скриншоте:

-68

Обновите страницу добавления поста:

http://127.0.0.1:8000/admin/blog/post/add/

Результат:

-69

Кликаем по имени автора.

Результат:

-70

Когда постов много удобно, чтобы была возможность отображать посты за определенный период времени. Например день, месяц, год. Давайте добавим такую возможность: поле фильтра по дате над списком постов.

Для этого отредактируйте файл blog/admin.py - как показано на скриншоте:

-71

Обновите страницу списка постов:

http://127.0.0.1:8000/admin/blog/post/

Результат:

-72

Теперь над списком постов есть удобный фильтр: все даты, текущий месяц, текущий день.

И вишенка на торте - добавим автоматическую сортировку постов от новых к старым для максимального удобства работы со списком постов.

Для этого отредактируйте файл blog/admin.py - как показано на скриншоте:

-73

Обновите страницу списка постов:

http://127.0.0.1:8000/admin/blog/post/

Результат:

-74

Теперь все посты отсортированы от новых к старым по убыванию. Супер.

Вот краткое описание для каждой строки в классе PostAdmin — это поможет лучше понять, как настраивается админка Django для модели Post:

list_display = ("title", "author", "publish", "status")

Показывает указанные поля в списке постов в админке (в виде колонок). Это позволяет быстро видеть заголовок, автора, дату публикации и статус каждого поста.

list_filter = ("status", "created", "publish", "author")

Добавляет боковую панель фильтров для указанных полей. Удобно фильтровать посты по дате создания, автору, статусу и дате публикации.

search_fields = ("title", "body")

Включает поисковую строку в админке, ищущую по заголовку и содержимому. Полезно, чтобы быстро найти нужный пост по ключевым словам.

prepopulated_fields = {"slug": ("title",)}

Автоматически заполняет поле slug на основе title. Например: при вводе "Привет, мир!" поле slug заполнится как privet-mir.

autocomplete_fields = ("author",)

Включает автодополнение поля author по имени пользователя. Особенно полезно, если пользователей много — не нужно листать длинный список.

raw_id_fields = ("author",)

Показывает author как ID-поле с кнопкой выбора, а не выпадающим списком. Это экономит ресурсы при большом количестве пользователей.

⚠️ Если указаны и autocomplete_fields, и raw_id_fields — Django будет использовать autocomplete (приоритетнее).

date_hierarchy = "publish"

Добавляет навигацию по датам (год/месяц/день) над списком постов. Удобно фильтровать и просматривать посты по времени публикации.

ordering = ("-publish",)

Сортирует посты по убыванию даты публикации. Новые посты отображаются первыми.

Список вопросов по настройке Django admin и регистрации моделей

которые часто задают на технических собеседованиях в крупных IT-компаниях (Яндекс, VK, Сбер, Ozon, Tinkoff и др.):

📌 Основы и регистрация моделей

  1. Как зарегистрировать модель в Django admin?
  2. В чём разница между admin.site.register(Model) и @admin.register(Model)?
  3. Что произойдёт, если не зарегистрировать модель в админке?
  4. Можно ли зарегистрировать одну и ту же модель с разными классами админки?
  5. Как скрыть модель от отображения в админке, но оставить возможность использовать inline-редактирование?

🧩 Настройка отображения и фильтрации

  1. Что делает list_display в ModelAdmin? Какие поля можно туда добавлять?
  2. В чём разница между list_display и list_filter?
  3. Как реализовать фильтр по пользовательскому полю, не входящему в модель напрямую?
  4. Что делает search_fields и какие ограничения у этого параметра?
  5. Можно ли делать search_fields по полям внешнего ключа (ForeignKey)? Как?

🛠 Форма редактирования объекта

  1. Что делает prepopulated_fields и как его правильно использовать?
  2. Как работает raw_id_fields и зачем его использовать?
  3. Чем отличается raw_id_fields от autocomplete_fields?
  4. Как заменить виджет для поля ForeignKey в админке?
  5. Как сделать зависимые поля (например, автозаполнение одного поля на основе другого)?

Навигация по датам и сортировка

  1. Как работает date_hierarchy и как выбрать правильное поле?
  2. Что делает параметр ordering в ModelAdmin?
  3. Как задать сортировку по связанному полю (related model)?

🔐 Права доступа и кастомизация

  1. Как скрыть некоторые поля от отображения в админке для разных ролей пользователей?
  2. Как ограничить доступ к модели только для суперпользователей или определённых групп?
  3. Как переопределить методы get_queryset() и has_change_permission() в админке?

📋 Продвинутые темы

  1. Как добавить кастомное поле в list_display, которого нет в модели?
  2. Как отфильтровать объекты в админке по текущему пользователю (request.user)?
  3. Как настроить inline-редактирование (StackedInline vs TabularInline)?
  4. Что такое admin actions? Как создать и зарегистрировать свою экшн-функцию в админке?
  5. Как изменить внешний вид и стиль Django admin (например, логотип, шрифты, тему)?

Работа с QuerySet и менеджерами

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

Объектно-реляционное отображение Django (ORM) — это мощный API для абстрагированной работы с базой данных, который позволяет легко создавать, получать, обновлять и удалять объекты.

ORM даёт возможность формировать SQL‑запросы, используя объектно-ориентированную парадигму Python. Иными словами, вы можете взаимодействовать с вашей базой данных в python3 стиле, а не писать «сырой» SQL вручную.

ORM сопоставляет модели с таблицами в базе данных и предоставляет простой, «python3» интерфейс для взаимодействия с ними. Он автоматически генерирует SQL‑запросы и сопоставляет результаты с экземплярами моделей. Django ORM поддерживает работу с базами данных MySQL, PostgreSQL, SQLite, Oracle и MariaDB.

Помните, что базу данных для проекта можно указать в настройке DATABASES в файле settings.py. Django умеет работать с несколькими базами данных одновременно, и при необходимости вы можете реализовать маршрутизацию данных с помощью database routers, создав свою схему обработки запросов.

После того как вы создадите модели данных, Django автоматически предоставит API для взаимодействия с ними. С полным описанием API моделей можно ознакомиться в официальной документации:

https://docs.djangoproject.com/en/5.0/ref/models/

Основой ORM Django являются QuerySet‑ы. QuerySet — это набор запросов к базе данных, который используется для извлечения объектов. Вы можете применять фильтры к QuerySet, чтобы сузить результаты поиска в зависимости от заданных параметров. QuerySet эквивалентен SQL-запросу SELECT, а фильтры соответствуют SQL‑конструкциям WHERE, LIMIT и другим.

Далее вы узнаете, как формировать и выполнять QuerySet‑ы.

Вопросы на собеседовании по теме ORM и QuerySet в Django

🟢 Junior / начинающий уровень:

  1. Что такое ORM в Django? Для чего он нужен?
  2. Что такое QuerySet? Как его получить?
  3. Чем отличается all() от filter()?
  4. Как написать запрос, который вернёт только записи, удовлетворяющие определённому условию?
  5. Что делает метод get()? Чем он отличается от filter(...).first()?
  6. Что произойдёт, если get() не найдёт объект? А если найдёт несколько?
  7. Как отсортировать записи в QuerySet?
  8. Как выбрать только определённые поля из модели?
  9. Зачем нужен values() и values_list()?
  10. Что такое exclude()? Как он работает?

🟡 Middle / средний уровень:

  1. Что такое ленивое выполнение (lazy evaluation) в QuerySet? Приведите пример.
  2. Чем отличается exists() от count()?
  3. Что делает annotate() и в каких случаях его используют?
  4. Что такое агрегация (aggregate())? Приведите пример с Sum, Avg, Count.
  5. Как использовать Q и F объекты в запросах?
  6. Что делает select_related() и в чём отличие от prefetch_related()?
  7. Как обновить несколько записей одним запросом?
  8. Как работает bulk_create() и когда стоит его использовать?
  9. Как получить только уникальные значения из поля модели?
  10. Что такое менеджер модели? Как создать кастомный менеджер?

🔴 Senior / продвинутый уровень:

  1. Как работает ORM под капотом? Как он превращает Python-код в SQL?
  2. Как отладить SQL-запрос, сгенерированный ORM?
  3. Когда следует отказаться от использования ORM в пользу «сырых» SQL‑запросов?
  4. Как реализовать сложную бизнес-логику внутри менеджера модели?
  5. Как избежать проблемы N+1 в Django?
  6. Как организовать работу с несколькими базами данных?
  7. Какие есть ограничения у Django ORM по сравнению с «чистым» SQL?
  8. Какие способы оптимизации QuerySet ты знаешь?
  9. Как реализовать собственные выражения (custom database expressions) в Django?
  10. Как безопасно выполнять сырые SQL‑запросы с параметрами через Django?

Создание объектов

Запустите следующую команду в терминале, чтобы открыть интерактивную оболочку Python:

python manage.py shell

Затем выполните в консоли следующие строки:

from django.contrib.auth.models import User
from blog.models import Post
user = User.objects.get(username='alex')
post = Post(title='Заголовок поста', slug='zagolovok-posta', body='основной текст поста.', author=user)
post.save()

Разберём, что делает этот код.

Сначала мы получаем объект пользователя с именем alex:

user = User.objects.get(username='alex')

Метод get() позволяет получить один объект из базы данных. За кулисами он выполняет SQL‑запрос SELECT. Обратите внимание, что метод ожидает ровно один результат.

  • Если запрос не вернёт ни одной записи, будет выброшено исключение DoesNotExist.
  • Если найдено несколько объектов — выбросится исключение MultipleObjectsReturned.

Обе ошибки являются атрибутами класса модели, на которой выполняется запрос.

Далее мы создаём экземпляр модели Post, указывая заголовок, слаг и текст публикации. В качестве автора мы передаём ранее полученного пользователя user:

post = Post(title='Заголовок поста', slug='zagolovok-posta', body='основной текст поста.', author=user)

Этот объект пока существует только в памяти — мы создали Python‑объект, который может использоваться в коде, но он ещё не сохранён в базе данных.

Чтобы сохранить его, вызываем метод save():

post.save()

Этот вызов выполняет SQL‑запрос INSERT и сохраняет объект в базе данных.

Таким образом, мы сначала создали объект в оперативной памяти, а затем сохранили его.

Однако можно создать и сразу сохранить объект в базе за один шаг, используя метод create():

from django.contrib.auth.models import User
from blog.models import Post
user = User.objects.get(username='alex')
Post.objects.create(title='Заголовок поста № 2', slug='zagolovok-posta-2', body='Основной текст поста', author=user)

Иногда может потребоваться получить объект, если он существует, или создать его, если его нет. В таких случаях используется метод get_or_create().

Он возвращает кортеж из двух значений:

  1. сам объект;
  2. логическое значение True, если объект был создан, и False, если найден существующий.

Пример:

user, created = User.objects.get_or_create(username='user2')

В этом коде будет выполнена попытка найти пользователя с именем user2. Если пользователь не найден — будет создан новый объект user2.

Вопросы на собеседовании: Создание объектов в Django ORM

🟢 Junior уровень:

  1. Как создать новый объект модели в Django и сохранить его в базу данных?
  2. Чем отличается save() от create()?
  3. Что делает метод get()?
  4. Что произойдёт, если get() не найдёт объект? А если найдёт несколько?
  5. Как работает get_or_create()? Что он возвращает?
  6. Когда использовать get_or_create(), а когда — сначала filter(), затем create()?
  7. Обязательно ли вызывать save() после create()?
  8. Как можно создать объект без сохранения в базу данных?
  9. Что будет, если вызвать save() дважды?
  10. Можно ли изменить уже существующий объект и сохранить изменения?

🟡 Middle уровень:

  1. Какие исключения может выбрасывать метод get()?
  2. Как узнать, был ли объект создан в get_or_create() или найден?
  3. Какие поля нужно указывать обязательно при создании объекта?
  4. Как задать значения полей по умолчанию при создании через create()?
  5. Как работает update_or_create()? Чем он отличается от get_or_create()?
  6. Что произойдёт, если вызвать get_or_create() в конкурентной среде (concurrent access)?
  7. Как добавить связанные объекты (например, ForeignKey) при создании модели?
  8. Как правильно использовать save() с флагами update_fields или force_insert?
  9. Можно ли переопределить метод save() в модели? Зачем это делать?
  10. Какие методы модели вызываются автоматически при создании и сохранении объекта?

🔴 Senior уровень:

  1. Какие проблемы могут возникнуть при массовом создании объектов? Как их избежать?
  2. Как избежать race condition при использовании get_or_create()?
  3. Как реализовать отложенное сохранение объекта с транзакцией (atomic)?
  4. Как кастомизировать поведение метода save() для бизнес-логики?
  5. Когда лучше использовать raw SQL INSERT, а не create() или save()?
  6. Как организовать создание объекта с вложенными отношениями (ForeignKey, ManyToMany)?
  7. Как правильно валидировать объект до сохранения в базу?
  8. Что такое bulk_create() и чем он отличается от обычного create()?
  9. Как поведение save() зависит от auto_now, auto_now_add и других специальных параметров поля?
  10. Можно ли отменить сохранение объекта на этапе выполнения save()?

Обновление объектов

Теперь изменим заголовок ранее созданного объекта Post и снова сохраним его:

from django.contrib.auth.models import User
from blog.models import Post
user = User.objects.get(username='alex')
post = Post(title='Заголовок поста', slug='zagolovok-posta', body='основной текст поста.', author=user)
post.title = 'Заголовок поста - обновленный'
post.save()

На этот раз метод save() выполнит SQL‑запрос UPDATE.

Любые изменения, которые вы вносите в объект модели, не сохраняются в базе данных, пока вы явно не вызовете метод save().

Вопросы на собеседовании: Обновление объектов в Django ORM

🟢 Junior уровень:

  1. Как изменить значение поля у объекта модели и сохранить его?
  2. Что делает метод save()?
  3. Когда save() выполняет INSERT, а когда — UPDATE?
  4. Что произойдёт, если вы измените объект, но не вызовете save()?
  5. Как обновить сразу несколько объектов в базе данных?

🟡 Middle уровень:

  1. Как работает параметр update_fields у метода save()?
  2. Как обновить все записи, удовлетворяющие условию, не загружая их в память?
  3. В чём разница между save() и update()?
  4. Что произойдёт, если вызвать update() без фильтрации (.all().update(...))?
  5. Чем bulk_update() отличается от обычного обновления?
  6. Что произойдёт, если изменить объект, но не вызвать save()?
  7. Как в Django можно обновить связанный объект (ForeignKey)?

🔴 Senior уровень:

  1. Как Django определяет, нужно ли выполнять INSERT или UPDATE при вызове save()?
  2. В каких случаях save() может привести к дополнительным SQL-запросам?
  3. Как реализовать частичное обновление объекта без перезаписи всех полей?
  4. Как избежать race condition при обновлении данных?
  5. Как можно оптимизировать массовое обновление записей?
  6. Что делает bulk_update() и какие у него ограничения?
  7. Что лучше использовать при обновлении большого количества строк — ORM или «сырой» SQL?
  8. Как работает F()-выражение при обновлении значения на основе текущего?

Извлечение объектов

Вы уже знаете, как получить один объект из базы данных с помощью метода get(). Мы использовали его так:

Post.objects.get(pk=1) # по первичному ключу
Post.objects.get(slug='zagolovok-posta') # по уникальному полю

Каждая модель Django имеет по крайней мере один менеджер — по умолчанию это objects. С помощью менеджера модели вы получаете объект QuerySet.

Метод get() используется, когда гарантированно существует только один объект, соответствующий условиям. Если объектов нет — он вызывает DoesNotExist. Если объектов больше одного — MultipleObjectsReturned.

Post.objects.get() # Без аргументов будет ошибка, если постов больше 1
Ошибка при вызове Post.objects.get() когда постов больше одного.
Ошибка при вызове Post.objects.get() когда постов больше одного.

Чтобы получить все объекты из таблицы, используйте метод all() от менеджера objects, например:

all_posts = Post.objects.all()

Так создаётся QuerySet, который вернёт все записи в таблице Post.

Обратите внимание: этот QuerySet ещё не выполнен.

Django использует отложенное выполнение (lazy evaluation) — это означает, что QuerySet будет выполнен только тогда, когда вы явно заставите его это сделать. Благодаря этому поведение QuerySet’ов остаётся очень эффективным.

Если вы не сохраните результат в переменную, а просто выполните выражение в интерактивной оболочке Python, то SQL-запрос будет выполнен сразу, потому что оболочка вынуждена сгенерировать вывод:

Post.objects.all()
Результат выполнения Post.objects.all() в командной строке.
Результат выполнения Post.objects.all() в командной строке.

Совет от эксперта:

Если вы часто работаете с большими таблицами, не вызывайте list(Post.objects.all()) без необходимости — это приведёт к загрузке всех записей в память.

Всегда фильтруйте или используйте пагинацию при работе с QuerySet.

from django.contrib.auth.models import User
from blog.models import Post
user = User.objects.get(username='alex')
Post.objects.filter(author=user)
Результат выполнения команды Post.objects.filter(author=user) для пользователя alex.
Результат выполнения команды Post.objects.filter(author=user) для пользователя alex.

user = User.objects.get(username='user2')
Post.objects.filter(author=user)
Результат выполнения команды Post.objects.filter(author=user) для пользователя user2. user2 - не подгтовил ни одного поста, поэтому QuerySet пустой.
Результат выполнения команды Post.objects.filter(author=user) для пользователя user2. user2 - не подгтовил ни одного поста, поэтому QuerySet пустой.

Вопросы на собеседовании: Получение объектов через get(), filter(), all()

🟢 Junior уровень:

  1. Чем отличается get() от filter()?
  2. Что произойдёт, если get() не найдёт ни одной записи?
  3. Что произойдёт, если get() найдёт несколько записей?
  4. Когда следует использовать get(), а когда — filter()?
  5. Что такое MultipleObjectsReturned и как с ним работать?
  6. Как получить все объекты модели?
  7. Как получить только первый результат из filter()?
  8. Что возвращает filter() и что возвращает get()?

🟡 Middle уровень:

  1. Как отловить исключения DoesNotExist и MultipleObjectsReturned?
  2. Как получить объект по уникальному полю (например, slug)?
  3. Как работает first() и last() у QuerySet?
  4. Можно ли использовать get() без параметров? Почему это не работает?
  5. Как безопасно получить объект, если он может не существовать?
  6. Как работает exists() в QuerySet?
  7. В чём разница между get(pk=...) и get(id=...)?

🔴 Senior уровень:

  1. Почему get() может быть опасным в продакшене при отсутствии ограничения уникальности?
  2. Как правильно логировать и отлаживать ошибки get() в большом проекте?
  3. Какие подводные камни могут быть при использовании get() в транзакциях?
  4. Как реализовать безопасную логику получения объекта с fallback через filter().first()?
  5. Как использовать get_or_create() или update_or_create() вместо get()?

Фильтрация объектов

Чтобы отфильтровать объекты, вы можете использовать метод filter() у менеджера модели. Этот метод позволяет задать условие SQL‑выборки (WHERE), используя field lookups — специальные выражения, связанные с полями модели.

Например, можно отфильтровать объекты Post по заголовку следующим образом:

Post.objects.filter(title='У лукоморья дуб зелёный')

Результат:

-79

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

Чтобы посмотреть, какой SQL‑запрос был сгенерирован, выполните:

posts = Post.objects.filter(title='У лукоморья дуб зелёный')
print(posts.query)

Результат:

-80
SELECT "blog_post"."id", "blog_post"."title", "blog_post"."slug"
FROM "blog_post"
WHERE "blog_post"."title" = 'У лукоморья дуб зелёный'

WHERE‑условие выполняет точное сравнение по полю title.

Также в сгенерированном SQL‑запросе может появиться ORDER BY, если в мета-опциях модели Post (в классе Meta) определено значение атрибута ordering. Мы не указывали порядок явно в QuerySet, поэтому применяется поведение по умолчанию.

Важно: атрибут query — не является частью публичного API QuerySet. Это означает, что он может измениться в будущих версиях Django и не гарантируется как стабильный интерфейс. Используйте его только для отладки.

Вопросы на собеседовании: фильтрация объектов и QuerySet в Django

🟢 Junior (начальный уровень):

  1. Что делает метод filter() в Django ORM?
  2. Что возвращает filter()? Чем он отличается от get()?
  3. Что произойдёт, если filter() не найдёт ни одной записи?
  4. Можно ли использовать filter() без параметров?
  5. Как отфильтровать записи по значению поля, например title='Example'?

🟡 Middle (средний уровень):

  1. Что такое field lookups? Назови 5 популярных (например: icontains, startswith, gte, in, range).
  2. Как отфильтровать записи, в которых подстрока содержится в значении поля?
  3. Как объединить несколько условий фильтрации (AND, OR)?
  4. Чем отличаются filter(), exclude(), и all()?
  5. Что такое ленивое выполнение (lazy evaluation) в QuerySet?
  6. Как узнать, какой SQL‑запрос будет выполнен ORM при использовании filter()?
  7. Что делает атрибут .query у QuerySet? Когда его безопасно использовать?
  8. Как использовать Q‑объекты для построения сложных условий?
  9. Как проверить, существует ли хотя бы одна запись, удовлетворяющая фильтру (exists())?
  10. Как отфильтровать объекты по связанным моделям (через ForeignKey)?

🔴 Senior (продвинутый уровень):

  1. Как влияет порядок методов filter(), exclude() и annotate() на результат?
  2. Какие подводные камни есть при фильтрации по связям (select_related, prefetch_related)?
  3. В каких случаях filter() приведёт к выполнению SQL‑запроса сразу?
  4. Как работает кэширование QuerySet и как оно влияет на цепочку фильтров?
  5. Почему .query не входит в публичный API? Какие риски при его использовании?
  6. Как профилировать и оптимизировать фильтрующие запросы?
  7. Чем filter().first() отличается от get() с точки зрения безопасности и производительности?
  8. Можно ли применять filter() к annotate()? Как меняется результат?
  9. Как реализовать фильтрацию по полям JSONField или ArrayField?
  10. Что произойдёт, если в filter() указать несуществующее имя поля?

Замена SQL Lite на PostgreSQL 16

В связи с тем, что даже в учебных целях SQL Lite весьма ограничен в своем функционале, принял решение установить базу данных PostgreSQL 16.

Подробную инструкцию как это сделать можно прочитать в отдельной статье на Дзен по ссылке:
https://dzen.ru/a/aEl0bGvEnjRyL8bC?share_to=link

Дальнейшее повествоание будет идти с учетом внесенных измнений в код проекта, который доступен по ссылке в GitHub:

Django-5/01-Blog/blog-project/README.md at main · myasoedas/Django-5
Список постов, которые добавил в базу заново, чтобы можно было делать запросы. Так же добавил пользователя user2 без административных прав с доступом в админку.
Список постов, которые добавил в базу заново, чтобы можно было делать запросы. Так же добавил пользователя user2 без административных прав с доступом в админку.

Django ORM — Field Lookups на реальных примерах

Пример 1: exact и iexact

Задача: найти пост с точным заголовком.

Post.objects.filter(title__exact="Письмо Татьяны к Онегину")

Задача: найти пост с точным заголовком нечувствительно к регистру:

Post.objects.filter(title__iexact="письмо татьяны к онегину")

Результат:

-82

Пример 2: contains и icontains

Задача: найти все посты, где в заголовке встречается слово "Биография" чувствительно к регистру.

Post.objects.filter(title__contains="Биография")

Задача: найти все посты, где в заголовке встречается слово "биография" нечувствительно к регистру.

Post.objects.filter(title__icontains="биография")

Этот код найдёт все посты, где есть слово "биография":

  • Биография М.Ю. Лермонтова
  • Биография русского поэта А.С. Пушкина

Результат:

-83

Пример 3: startswith, istartswith endswith

Задача: найти посты, начинающиеся со слова «Заголовок» чувствительно к регистру.

Post.objects.filter(title__startswith="Заголовок")

Задача: найти посты, начинающиеся со слова «Заголовок» нечувствительно к регистру.

Post.objects.filter(title__istartswith="ЗагоЛовок")

Результат:

-84

Пример 4: endswith, iendswith

Задача: найти посты, заканчивающиеся словом «Пушкина» чувствительно к регистру.

Post.objects.filter(title__endswith="Пушкина")

Задача: найти посты, заканчивающиеся словом «Пушкина» нечувствительно к регистру.

Post.objects.filter(title__iendswith="ПуШкиНа")

Результат:

-85

Пример 5: in

Задача: найти посты по нескольким ID.

Post.objects.filter(id__in=[1, 3, 7])

Результат:

-86

Пример 6: gt больше чем (после 16:30)

Задача: найти посты опубликованные после 16:30.

from datetime import datetime
# naive datetime!
Post.objects.filter(publish__gt=datetime(2025, 6, 13, 16, 30))

У нас активирована поддержка часовых поясов в Django (USE_TZ = True), а мы передаём naive datetime — объект datetime, не привязанный к часовому поясу. Django выдаст предупреждение, что сравнение с датой без таймзоны может привести к ошибкам, особенно при фильтрации по полям DateTimeField.

Используйте timezone-aware datetime, чтобы избежать предупреждения и обеспечить корректное сравнение.

Результат с предупрждением:

Django выдал предупреждение о возможной ошибке при сравнении времени.
Django выдал предупреждение о возможной ошибке при сравнении времени.

Рассмотрим примеры, как правильно работать со временем из тайм зоны МСК.

Задача: найти посты, опубликованные после 13 июня 2025 года, 16:30 по Москве.

Шаг 1: Импортируйте timezone из Django

from django.utils import timezone
from datetime import datetime

Шаг 2: Создайте aware-дату

aware_dt = timezone.make_aware(datetime(2025, 6, 13, 16, 30))

Шаг 3: Применяйте фильтрацию по времени МСК безопасно

Post.objects.filter(publish__gt=dt)

Результат:

Вывод результата команды где есть ошибка и без ошибки. Всегда помните об особенностях работы со временем из тайм зоны.
Вывод результата команды где есть ошибка и без ошибки. Всегда помните об особенностях работы со временем из тайм зоны.

Что происходит под капотом?

  • Когда USE_TZ=True, все даты в базе считаются в UTC.
  • datetime(2025, 6, 13, 16, 30) считается naive (без tzinfo).
  • timezone.make_aware(...) добавляет таймзону (обычно settings.TIME_ZONE, например, Europe/Moscow).

Альтернатива: использовать timezone.now() для динамики:

Post.objects.filter(publish__gt=timezone.now())

Частый вопрос на собеседовании:

В чём разница между naive и aware datetime в Django и как это влияет на фильтрации в QuerySet?

Пример 6: lt меньше чем (16:30)

Задача: Найти посты, опубликованные до 13 июня 2025 года, 16:30 по Москве.

aware_dt = timezone.make_aware(datetime(2025, 6, 13, 16, 30))
Post.objects.filter(publish__lt=aware_dt)

Результат:

-89

Пример 7: range — от и до

Задача: найти посты, опубликованные в интервале с 12 июня 12:00 до 13 июня 16:30 по Москве.

aware_start_dt = timezone.make_aware(datetime(2025, 6, 12, 12, 0))
aware_end_dt = timezone.make_aware(datetime(2025, 6, 13, 16, 30))
Post.objects.filter(publish__range=(aware_start_dt, aware_end_dt))

Результат:

-90

Как проверить текущее время в вашей системе с учётом таймзоны?

В чем разница между datetime.now() и timezone.now()?

datetime.now()

Результат:

datetime.datetime(2025, 6, 13, 18, 46, 30, 410965)

datetime.now()

  • Возвращает naive datetimeлокальное время машины (в твоем случае это +03:00, Москва).
  • 2025-06-13 18:46:30.410965:
    18 часов, 46 минут, 30 секунд, 410965 микросекунд.
  • Не содержит информацию о временной зоне, что может привести к ошибкам при сравнении/сохранении в БД, если USE_TZ = True.
timezone.now()

Результат:

datetime.datetime(2025, 6, 13, 15, 47, 1, 550860, tzinfo=datetime.timezone.utc)

timezone.now()

  • Возвращает aware datetime — время в UTC, но с информацией о временной зоне (tzinfo=datetime.timezone.utc).
  • 2025-06-13 15:47:01.550860 — то же самое время, но в UTC (на 3 часа меньше, чем московское).

Почему это важно:

Если в settings.py стоит:

USE_TZ = True
TIME_ZONE = 'Europe/Moscow'

— то Django сохраняет время в базе в UTC, а при выводе переводит в Europe/Moscow.

Поэтому ты получаешь два разных времени:

datetime.now(): 2025-06-13 18:46:30.410965 (naive) Москва (локальное)

timezone.now(): 2025-06-13 15:47:01.550860 (aware, UTC) UTC

⚠️ Важное правило:

Если USE_TZ = True, всегда используй timezone.now(), чтобы избежать ошибок при фильтрации по дате.

Примеры фильтрации Post с учётом таймзоны

Пример 1: найти все опубликованные посты (status=PUBLISHED).

from blog.models import Post
posts = Post.objects.filter(status=Post.Status.PUBLISHED)

Результат:

-91

Пример 2: найти отложенные посты, опубликованные в будущем.

from django.utils import timezone
future_posts = Post.objects.filter(publish__gt=timezone.now())

Результат:

Нет отложенных постов, опубликованные в будущем.
Нет отложенных постов, опубликованные в будущем.

👉 Используется timezone.now() — aware datetime в UTC, как и поле publish.

Пример 3: Найти посты, опубликованные сегодня (по московскому времени).

from django.utils import timezone
# Приводим к локальной зоне (Москва)
aware_now = timezone.localtime(timezone.now())
aware_start_of_day = aware_now.replace(hour=0, minute=0, second=0, microsecond=0)
aware_end_of_day = aware_now.replace(hour=23, minute=59, second=59, microsecond=999999)
posts_today = Post.objects.filter(
publish__range=(aware_start_of_day, aware_end_of_day),
status=Post.Status.PUBLISHED,
)

Результат:

-93

⚠️ Обратите внимание: если использовать range — оба значения должны быть aware datetime, иначе будет warning.

Пример 4: найти все посты за последние 7 дней.

from datetime import timedelta
from django.utils import timezone
aware_week_ago = timezone.now() - timedelta(days=7)
recent_posts = Post.objects.filter(
publish__gte=aware_week_ago,
status=Post.Status.PUBLISHED,
)

Результат:

-94

Пример 5: найти посты с точным временем публикации - 16:30 МСК 13 июня 2025 г. (обновлённый код под Django 5+ и Python 3.12)

from datetime import datetime
from zoneinfo import ZoneInfo
from django.utils.timezone import make_aware
from blog.models import Post # или откуда у тебя импорт модели
# Создаём "осознанную" дату с временной зоной Москвы
naive_datetime = datetime(2025, 6, 13, 16, 30)
moscow_zone = ZoneInfo("Europe/Moscow")
target_time = make_aware(naive_datetime, timezone=moscow_zone)
# Фильтрация по точному времени публикации
exact_posts = Post.objects.filter(publish=target_time)
-95

Результат: постов нет, так как время ближайшего поста: 16:30:02 МСК.

Пример 6: Найдите все посты, опубликованные от 16:30 до 16:30:59 МСК 13 июня 2025 г. (обновлённый код под Django 5+ и Python 3.12).

from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from django.utils.timezone import make_aware
aware_start = make_aware(datetime(2025, 6, 13, 16, 30), timezone=ZoneInfo("Europe/Moscow"))
aware_end = aware_start + timedelta(seconds=59)
posts = Post.objects.filter(publish__range=(aware_start, aware_end))
for post in posts:
print(post.title, post.publish)

Результат:

Обратите внимание на последнюю строку там время в UTC, а не локальное время: post.publish = 2025-06-13 13:30:02+00:00 (UTC) == 2025-06-13 16:30:02+03:00 (МСК)
Обратите внимание на последнюю строку там время в UTC, а не локальное время: post.publish = 2025-06-13 13:30:02+00:00 (UTC) == 2025-06-13 16:30:02+03:00 (МСК)

Что показывает 2025-06-13 13:30:02+00:00?

Это время в UTC (временная зона +00:00), которое Django показывает в базе данных или shell при включённой поддержке временных зон. В нашем случае:

post.publish = 2025-06-13 13:30:02+00:00 (UTC) == 2025-06-13 16:30:02+03:00 (МСК)

Почему так получилось?

1. Мы вводили дату в МСК:

datetime(2025, 6, 13, 16, 30), timezone=ZoneInfo("Europe/Moscow")

2. Django при сохранении приводит DateTimeField к UTC.

  • В БД дата хранится в UTC (13:30:02).
  • Когда вы смотрите post.publish — он возвращает UTC-дату (если не использовать localtime()).

Как всегда видеть локальное время в shell?

Импортируй django.utils.timezone.localtime:

from django.utils.timezone import localtime
for post in posts:
print(post.title, localtime(post.publish))

Результат:

Теперь время отображается так, как мы ожидаем при нашем запросе: datetime(2025, 6, 13, 16, 30), timezone=ZoneInfo("Europe/Moscow")
Теперь время отображается так, как мы ожидаем при нашем запросе: datetime(2025, 6, 13, 16, 30), timezone=ZoneInfo("Europe/Moscow")

Пример 8: isnull

Задача: представим, что поле author может быть пустым (анонимные посты).

Post.objects.filter(author__isnull=True)

Полнотекстовый поиск в Django 5 на русском языке (PostgreSQL 16)

Выделил эту тему в отдельную статью. Использование полнотекстового поиска и триграм может увеличить скорость ORM запросов Django на больших данных в 100 раз по сравнению с обычными классическими методами поиска.

Кроме того эти технологии позволяют искать нужную информацию, как на латинице так и на русском с ошибками в словах и без.

Рекомендую ознакомиться с этими технологиями - это будет полезно. Так же в этой статье вы найдете материал по профилировке запросов в PostgreSQL. Профилировка запросов помогает найти узкое горлышко в ваших запросах и решить проблему медленных запросов.

Я прошел весь Дзен - статьи имеют лимит на фото - продолжение будуписать отдельными статьями и добавлять ниже по порядку следования

Field lookups в Django 5: поиск, фильтрация, сортировка и работата с датами