Перейти к основному содержимому

Первая программа на Django

Разработчику Архитектору

Play ITЗагрузка интерактивного демо…


Первая программа на Django

Где применяют Django

Django — "батарейки в комплекте" для веба на Python — ORM, админка, формы, auth, миграции. Если Flask — гибкий минимум, то Django сразу задаёт структуру проекта (manage.py, приложения, settings.py).

Архитектура MTV — Model (данные), Template (HTML), View (логика запроса). После этой статьи проще читать FastAPI для чистого API без HTML.


Что получится

Проект с приложением main, моделью сообщений, страницей в браузере и встроенной админкой /admin. Материал согласован с официальным туториалом Django 6 (части 1–8): там опросы (polls), здесь — доска сообщений (main), но идеи те же.

Проект и приложение

В Django это разные уровни:

УровеньЧто этоПример
ПроектНастройки сайта: БД, INSTALLED_APPS, корневой urls.pymy_first_django_project/
ПриложениеМодуль с конкретной функцией: модели, views, шаблоныmain/

Один проект может содержать много приложений (блог, опросы, каталог). Одно приложение можно перенести в другой проект — для этого его делают самодостаточным: свои templates/main/, static/main/, urls.py с app_name. Подробнее — в гайде по reusable apps.


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

Для начала работы требуется среда выполнения Python. Рекомендуется использовать виртуальное окружение для изоляции зависимостей конкретного проекта от системных библиотек компьютера. Виртуальное окружение предотвращает конфликты версий пакетов между разными проектами.

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

Код ITЗагрузка примера кода…

Разбор: сначала подготавливается окружение и каркас проекта, затем runserver поднимает локальный сервер на 127.0.0.1:8000.

Команда django-admin startproject создаёт каркас. Типичная структура:

my_first_django_project/
manage.py # CLI: runserver, migrate, shell, test
my_first_django_project/
__init__.py
settings.py # БД, приложения, шаблоны, DEBUG
urls.py # корневые маршруты («оглавление» сайта)
asgi.py / wsgi.py # точки входа для продакшн-серверов

manage.py — обёртка над django-admin для этого проекта — подставляет DJANGO_SETTINGS_MODULE, чтобы команды знали, где settings.py.

Проверка установки: python -m django --version. Django 6 требует Python 3.12+.

Имена проектов: не называйте каталог django или test — конфликт с пакетами Python.

После runserver откройте http://127.0.0.1:8000/ — страница "Congratulations!". До первого migrate в консоли может быть предупреждение о неприменённых миграциях — это нормально.

Dev-сервер перезагружает Python-код при изменениях; после добавления новых файлов иногда нужен ручной перезапуск. В продакшене этот сервер не используют — только WSGI/ASGI (Gunicorn, uvicorn и т.д.).


Структура приложения

Проект Django состоит из одного или нескольких приложений. Приложение — Python-пакет с моделями, views, шаблонами и тестами. Создаётся из каталога с manage.py:

cd my_first_django_project
python manage.py startapp main

Команда создаёт каталог main/:

main/
__init__.py
admin.py # регистрация моделей в админке
apps.py # класс конфигурации приложения
migrations/ # история изменений схемы БД
models.py # модели ORM
tests.py # автотесты
views.py # обработчики HTTP-запросов

Регистрация в settings.py — иначе Django не увидит миграции и админку:

# my_first_django_project/settings.py

INSTALLED_APPS = [
'main.apps.MainConfig', # предпочтительно: явный класс конфигурации
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]

Разбор: INSTALLED_APPS — список активных приложений. Встроенные django.contrib.* дают админку, auth, сессии, статику. Короткая запись 'main' тоже работает, но 'main.apps.MainConfig' явно указывает класс из apps.py — так принято в туториале и в reusable-приложениях.

INSTALLED_APPS также определяет, откуда Django ищет шаблоны (APP_DIRS=True) и статику (AppDirectoriesFinder).


Определение модели данных

Модель — единственный источник правды о данных (принцип DRY): из models.py Django автоматически строит SQL-схему, миграции и Python API для чтения/записи. Классы наследуют models.Model; атрибут класса = столбец в таблице.

По умолчанию в settings.py — SQLite (db.sqlite3 в корне проекта). Для продакшена чаще PostgreSQL; на старте SQLite удобен — ничего не устанавливать. Заодно выставьте TIME_ZONE под свой регион.

Создадим модель сообщений в main/models.py.

# main/models.py
from django.db import models

class Message(models.Model):
text = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
is_published = models.BooleanField(default=True)

def __str__(self):
return self.text

Разбор — Message описывает таблицу БД, поля класса становятся столбцами, а __str__ задаёт читаемое имя объекта в админке и shell.

Поле CharField определяет строковое поле максимальной длины 200 символов. Поле DateTimeField хранит дату и время создания записи, параметр auto_now_add=True автоматически устанавливает текущее время при создании объекта. Поле BooleanField хранит логическое значение истинности или ложности, по умолчанию принимая значение True. Метод __str__ возвращает строковое представление объекта, что удобно для отображения в административной панели и консоли.

Трёхшаговый цикл миграций

Любое изменение модели повторяет один и тот же цикл:

  1. Правите models.py.
  2. python manage.py makemigrations main — создаёт файл в main/migrations/.
  3. python manage.py migrate — применяет к БД.
python manage.py makemigrations main
python manage.py migrate

makemigrations сравнивает модели с последней миграцией и пишет diff на диск. migrate читает таблицу django_migrations и выполняет только новые шаги. Миграции коммитят в git вместе с кодом — так схема обновляется у коллег и на сервере.

Полезные команды:

python manage.py sqlmigrate main 0001 # какой SQL выполнит миграция (без запуска)
python manage.py check # проверка проекта без БД

На свежем проекте первый migrate создаёт таблицы встроенных приложений (auth, admin, sessions и др.) — до этого runserver может предупреждать о неприменённых миграциях.

Интерактивная оболочка ORM

manage.py shell загружает настройки проекта и модели — удобнее обычного python:

python manage.py shell
>>> from main.models import Message
>>> Message.objects.all()
<QuerySet []>
>>> m = Message(text="Привет, Django!")
>>> m.save()
>>> m.id
1
>>> Message.objects.filter(text__icontains="django")
<QuerySet [<Message: Привет, Django!>]>

Метод __str__ важен не только в shell: админка и логи показывают именно его. Без __str__ объекты выглядят как <Message object (1)>.

Связи между таблицами задают ForeignKey, ManyToManyField, OneToOneField. В туториале опросов вариант ответа (Choice) ссылается на вопрос (Question) через ForeignKey(..., on_delete=models.CASCADE) — при удалении вопроса удаляются и его варианты.


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

View — функция или класс, который получает HttpRequest и возвращает HttpResponse (HTML, редирект, JSON, 404). Всё остальное — на ваше усмотрение — ORM, шаблоны, сторонние библиотеки.

Минимальный view без шаблона (как в части 1 туториала):

from django.http import HttpResponse

def index(request):
return HttpResponse("Hello, world. You're at the main index.")

Для списка сообщений используем шаблон — main/views.py:

# main/views.py
from django.shortcuts import render
from .models import Message

def message_list(request):
messages = Message.objects.all()
context = {
'messages': messages,
}
return render(request, 'main/message_list.html', context)

Разбор: Message.objects.all() формирует QuerySet сообщений, context передаёт его в шаблон, render возвращает готовый HTTP-ответ.

Функция принимает объект запроса request. Она обращается к базе данных через менеджер Message.objects.all(), который возвращает все объекты модели. Данные передаются в словарь контекста. Функция render объединяет шаблон HTML с контекстом и формирует ответ HTTP.


Настройка маршрутизации

URLconf — "оглавление" сайта: список path() в urls.py. Django обходит urlpatterns сверху вниз, находит совпадение и вызывает view.

Запрос GET / на нашем проекте:

  1. Загружается корневой urls.py (ROOT_URLCONF).
  2. Совпадает path('', include('main.urls')) — префикс '' отрезается, остаток '' уходит в main/urls.py.
  3. Там совпадает path('', views.message_list, ...) → вызывается message_list(request).

Маршруты приложения — в main/urls.py:

from django.urls import path
from . import views

app_name = 'main' # пространство имён URL — важно при нескольких приложениях

urlpatterns = [
path('', views.message_list, name='message_list'),
# пример детальной страницы по id:
# path('<int:pk>/', views.message_detail, name='message_detail'),
]

<int:pk>конвертер: из URL извлекается целое и передаётся в view как pk=…. Другие конвертеры — str, slug, uuid.

Подключение в корневом urls.py:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
path('admin/', admin.site.urls),
path('', include('main.urls')),
]

Разбор: include() подключает маршруты приложения — их можно повесить на любой префикс (polls/, board/). Исключение — admin.site.urls: готовый URLconf админки, без include.

name='message_list' нужен для reverse() в Python и {% url %} в шаблонах — не привязывайтесь к жёстким путям.

Во view для детальной страницы удобен shortcut:

from django.shortcuts import get_object_or_404

def message_detail(request, pk):
message = get_object_or_404(Message, pk=pk)
return render(request, 'main/message_detail.html', {'message': message})

get_object_or_404 заменяет связку get() + Http404 — стандартный приём из части 3 туториала.


Создание шаблона

Шаблон отделяет вёрстку от Python. Django ищет файлы в templates/ каждого приложения из INSTALLED_APPS (настройка APP_DIRS).

Пространство имён шаблонов: путь main/templates/main/message_list.html, в коде — 'main/message_list.html'. Вложенная папка main/ обязательна: иначе при двух приложениях с файлом index.html Django возьмёт первый попавшийся.

Создайте main/templates/main/message_list.html.

Код ITЗагрузка примера кода…

Разбор — {% if %} проверяет наличие сообщений, {% for %} перебирает список, {{ ... }} выводит значения, фильтр |date форматирует дату.

Теги {% %} — логика (if, for, url). {{ message.text }} — вывод переменной; точка означает атрибут или ключ словаря. В цикле {% for message in messages %} доступен forloop.counter.

Ссылки без жёсткого пути:

<a href="{% url 'main:message_detail' message.id %}">{{ message.text }}</a>

main: — префикс из app_name; имя маршрута — message_detail. Смените URL в urls.py — шаблоны трогать не нужно.


Административная панель

Админка — интерфейс для сотрудников, не для посетителей сайта. Django генерирует формы из моделей — CharField → текстовое поле, DateTimeField → календарь с "Сегодня"/"Сейчас", история изменений по записям. Идея из новостных редакций: контент-менеджеры правят данные, публика видит готовые страницы.

Создайте суперпользователя:

python manage.py createsuperuser

Команда запросит имя пользователя, адрес электронной почты и пароль. После ввода данных можно войти по адресу http://127.0.0.1:8000/admin/.

Чтобы управлять нашей моделью Message, необходимо зарегистрировать её в админке. Файл регистрации находится в main/admin.py.

# main/admin.py
from django.contrib import admin
from .models import Message

@admin.register(Message)
class MessageAdmin(admin.ModelAdmin):
list_display = ('text', 'created_at', 'is_published')
list_filter = ('is_published', 'created_at')
search_fields = ('text',)
fieldsets = [
(None, {'fields': ['text']}),
('Публикация', {'fields': ['is_published', 'created_at']}),
]

Разбор:

  • list_display — столбцы списка (можно метод модели; для сортировки — декоратор @admin.display).
  • list_filter — боковые фильтры (для дат: "Сегодня", "7 дней").
  • search_fields — поиск по LIKE.
  • fieldsets — группы полей на форме редактирования.

Если у модели есть связанные объекты (как Choice у Question в туториале), их добавляют inline (TabularInline / StackedInline) — дочерние записи редактируются на странице родителя.


Добавление сообщения через ModelForm

Публичная форма на сайте удобнее, чем только админка. ModelForm строится по полям модели и выполняет ту же валидацию, что и ORM.

Правило веб-разработки (часть 4 туториала) — формы, меняющие данные на сервере, отправляют method="post". GET только читает. После успешного POST — редирект (HttpResponseRedirect), чтобы обновление страницы не отправило форму повторно (паттерн POST-Redirect-GET).

main/forms.py:

from django import forms
from .models import Message

class MessageForm(forms.ModelForm):
class Meta:
model = Message
fields = ['text']
widgets = {
'text': forms.Textarea(attrs={'rows': 3, 'placeholder': 'Ваше сообщение'}),
}

Представление с обработкой GET и POST:

Код ITЗагрузка примера кода…

В шаблоне перед списком:

<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Отправить</button>
</form>

Без {% csrf_token %} Django вернёт 403 на POST — встроенная защита от CSRF.

Во view после сохранения:

from django.urls import reverse
from django.http import HttpResponseRedirect

def message_list(request):
if request.method == 'POST':
form = MessageForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('main:message_list'))
else:
form = MessageForm()
messages = Message.objects.all()
return render(request, 'main/message_list.html', {
'messages': messages,
'form': form,
})

request.POST — словарь отправленных полей (значения всегда строки). reverse('main:message_list') строит URL по имени маршрута — не хардкодьте / в коде.

При ошибке валидации снова рендерите ту же страницу с form.errors — пользователь видит, что не так.

Class-Based Views (кратко)

Повторяющийся шаблон "взять объект из БД → отрендерить шаблон" закрывают generic views:

from django.views import generic

class MessageListView(generic.ListView):
model = Message
template_name = 'main/message_list.html'
context_object_name = 'messages'

ListView и DetailView сокращают код списков и карточек — в туториале ими заменяют index/detail после того, как понятна ручная версия. Кастомная логика — в get_queryset().

Дальше по сложности: Справочник по Django (formsets, CBV, пагинатор, вход пользователя).


Статические файлы

CSS, JavaScript и изображения — статика. В dev Django раздаёт их через django.contrib.staticfiles; в продакшене — python manage.py collectstatic и веб-сервер или CDN.

Структура с пространством имён (как у шаблонов):

main/static/main/style.css

В шаблоне:

{% load static %}
<link rel="stylesheet" href="{% static 'main/style.css' %}">

Тег {% static %} подставляет URL с учётом STATIC_URL. Внутри .css ссылайтесь на картинки относительными путями (url("images/bg.png")), не через {% static %} — файл не обрабатывается шаблонизатором.


Автотесты

Тесты фиксируют ожидаемое поведение: после правки urls.py или view достаточно python manage.py test — секунды вместо ручного клика по сайту. Django создаёт отдельную тестовую БД, прогоняет методы с именем test_* и удаляет её.

main/tests.py:

from django.test import TestCase
from django.urls import reverse
from .models import Message

class MessageModelTests(TestCase):
def test_str_returns_text(self):
m = Message(text="Тест")
self.assertEqual(str(m), "Тест")

class MessageViewTests(TestCase):
def test_list_page_returns_200(self):
response = self.client.get(reverse('main:message_list'))
self.assertEqual(response.status_code, 200)

def test_post_creates_message(self):
response = self.client.post(reverse('main:message_list'), {'text': 'Новое'})
self.assertEqual(response.status_code, 302) # редирект после POST
self.assertEqual(Message.objects.count(), 1)

def test_empty_list_context(self):
response = self.client.get(reverse('main:message_list'))
self.assertEqual(list(response.context['messages']), [])

self.client имитирует браузер — get, post, проверка status_code, assertContains, response.context. Имена маршрутов с app_name — с префиксом 'main:'.

Запуск: python manage.py test main или весь проект — python manage.py test.


Пошаговый запуск

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

  1. Установите Python с python.org.
  2. Создайте и активируйте виртуальное окружение.
  3. Настройте проект, приложение, INSTALLED_APPS, urls.py и модели в коде.
  4. В каталоге проекта:
pip install django
django-admin startproject my_first_django_project
cd my_first_django_project
python manage.py startapp main
python manage.py makemigrations
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver

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


Частые ошибки

СимптомПричина
no such tableНе сделали makemigrations / migrate
TemplateDoesNotExistНеверный путь в render() или нет templates/main/
404 на / при работающем /polls/Маршрут приложения не подключён через include()
NoReverseMatchНет name в path() или неверный app_name в {% url %}
Статика не грузитсяВ dev — DEBUG=True и staticfiles в INSTALLED_APPS; в prod — collectstatic
403 CSRF на POSTВ форме нет {% csrf_token %}
Двойная отправка формыПосле POST нет редиректа — нужен HttpResponseRedirect

Что попробовать

  1. Message.objects.filter(text__icontains='...') и order_by('-created_at')[:10] — поиск и срез в view.
  2. F('votes') + 1 при инкременте счётчика — атомарное обновление в БД (часть 4 туториала).
  3. python manage.py test main — тесты модели и страницы (раздел "Автотесты").
  4. django-debug-toolbar — панели SQL, шаблонов, времени (часть 8 туториала).
  5. REST без шаблонов: Django REST Framework или FastAPI.

Дальше


Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.


Модель запроса в Django

Цепочка повторяется на большинстве страниц:

  1. Браузер запрашивает URL → корневой urls.pyinclude()main/urls.py.
  2. path() сопоставляет шаблон и вызывает view с request (и параметрами вроде pk).
  3. View читает/пишет через ORM (Message.objects…) или обрабатывает request.POST.
  4. Ответ — render() (HTML), HttpResponseRedirect (после POST) или JSON.

Когда схема привычна, проще читать Django REST Framework и отличать серверный HTML от API.


Что добавить после базового MVP

После первой рабочей версии полезно внедрить "производственные" привычки:

  • Переменные окруженияSECRET_KEY, DEBUG, параметры БД вне settings.py.
  • Права доступа — кто может публиковать сообщения, кто только читать.
  • Покрытие тестами — модель, список, POST, 404 на несуществующий pk.
  • Статика и шаблоны админки — кастомизация admin/base_site.html в templates/admin/ проекта (часть 7 туториала).
  • Reusable app — вынести main в отдельный пакет с pyproject.toml, если планируете переиспользовать код (advanced tutorial).

Полезные переходы по энциклопедии


В подборках

Статья входит в тематические подборки и блок "С чего начать?" на главной. Соседние шаги того же маршрута:

Первые шаги (маршрут подборки) — Первая программа на Next.js, Первая программа на Django REST Framework, Первая программа на Ext JS, Первая программа на Tkinter, Первая программа на Angular, Первая программа на PyQt6.