Функциональные представления в Django: первый рабочий обработчик
Как написать первое function-based view в Django 5.x: от простого текстового ответа до страницы со списком объектов из базы. С проверками на каждом шаге и разбором типичных ошибок.

За полчаса вы получите три работающих представления на функциях: одно отдаёт текст, второе — HTML-шаблон, третье — список записей из базы данных. Всё это на Django 5.x. Понадобится только установленный Python 3.10+ и созданный проект — если проекта ещё нет, ниже показано, как его получить с нуля.
Представление (view) в Django — это функция, которая принимает запрос и возвращает ответ. В формате «на функциях» это буквально обычная функция Python с одним обязательным аргументом request. Есть ещё «представления на классах» (class-based views), но для первого обработчика функции проще: видно, что происходит, без наследования и магии.
Что нужно до начала
- Python 3.10 или новее (проверьте командой
python --version). - Установленный Django 5.x. Если его нет:
pip install "Django>=5.0,<6.0". - Созданный проект и приложение внутри него. Команды ниже, если начинаете с чистого листа.
- Терминал, открытый в папке проекта, и любой текстовый редактор.
Создаём проект и приложение, если их ещё нет:
django-admin startproject mysite
cd mysite
python manage.py startapp blog
После этого рядом с manage.py появится папка mysite/ (настройки проекта) и папка blog/ (наше приложение). Дальше добавьте приложение в список INSTALLED_APPS в файле mysite/settings.py — впишите строку 'blog', в этот список. Без этого шага миграции и шаблоны приложения работать не будут.
Шаг 1. Простейшее представление, которое отдаёт текст
Откройте файл blog/views.py и замените его содержимое:
from django.http import HttpResponse
def hello(request):
# request — объект входящего запроса, его передаёт Django
return HttpResponse("Привет, это первое представление на функции")
Функция обязана вернуть объект ответа. Здесь это HttpResponse с простой строкой. Пока этот код никуда не подключён — Django не знает, по какому адресу его вызывать. Это следующий шаг.
Шаг 2. Привязать представление к адресу
Создайте в приложении файл blog/urls.py (его там пока нет) и впишите:
from django.urls import path
from . import views
urlpatterns = [
path("hello/", views.hello, name="hello"),
]
Теперь подключите этот файл в главный mysite/urls.py. Он уже содержит маршрут для админки — добавьте к нему свой:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("", include("blog.urls")),
]
Запустите сервер разработки:
python manage.py runserver
Проверка: откройте в браузере http://127.0.0.1:8000/hello/. Должна появиться строка «Привет, это первое представление на функции». Если открыть корень http://127.0.0.1:8000/ — увидите ошибку 404, потому что для пустого пути мы обработчик не назначили. Это нормально.
Шаг 3. Отдать HTML-шаблон вместо голого текста
Голая строка годится для проверки, но реальные страницы собирают из шаблонов. Создайте папку blog/templates/blog/ и в ней файл hello.html:
<!DOCTYPE html>
<html lang="ru">
<head><title>{{ title }}</title></head>
<body>
<h1>{{ title }}</h1>
<p>Эту страницу собрал шаблон.</p>
</body>
</html>
Вложенная папка blog/ внутри templates/ — не лишняя: она разводит шаблоны разных приложений по пространствам имён, чтобы файлы с одинаковыми именами не перекрывали друг друга. Теперь измените представление:
from django.shortcuts import render
def hello(request):
context = {"title": "Страница из шаблона"}
return render(request, "blog/hello.html", context)
Функция render — сокращение: она находит шаблон, подставляет в него данные из context и возвращает готовый HttpResponse. Django ищет шаблоны в папках templates/ установленных приложений автоматически, потому что в settings.py в настройке TEMPLATES по умолчанию включён параметр APP_DIRS со значением True.
Проверка: обновите /hello/ в браузере. Теперь это полноценная HTML-страница с заголовком «Страница из шаблона» во вкладке и на самой странице.
Шаг 4. Показать список записей из базы
Добавим модель и выведем её объекты. В blog/models.py:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.title
Примените миграции, чтобы таблица появилась в базе:
python manage.py makemigrations blog
python manage.py migrate
Добавьте пару записей через оболочку, чтобы было что показывать:
python manage.py shell
from blog.models import Post
Post.objects.create(title="Первый пост")
Post.objects.create(title="Второй пост")
exit()
Теперь представление, которое достаёт записи и отдаёт их в шаблон:
from django.shortcuts import render
from .models import Post
def post_list(request):
posts = Post.objects.all().order_by("-created")
return render(request, "blog/post_list.html", {"posts": posts})
Шаблон blog/templates/blog/post_list.html:
<h1>Записи</h1>
<ul>
{% for post in posts %}
<li>{{ post.title }} — {{ post.created|date:"d.m.Y H:i" }}</li>
{% empty %}
<li>Пока пусто</li>
{% endfor %}
</ul>
И маршрут в blog/urls.py:
urlpatterns = [
path("hello/", views.hello, name="hello"),
path("posts/", views.post_list, name="post_list"),
]
Проверка: откройте http://127.0.0.1:8000/posts/. Увидите список из двух записей, отсортированный по дате: сначала более новая. Если записей нет — выведется «Пока пусто».
Представление на функции — это контракт из двух слов: приняло request, вернуло HttpResponse. Всё между ними — ваш код и никакой магии.
Принять параметр из URL
Часто адрес несёт данные — например, номер записи. Django передаёт такие части URL в представление как именованные аргументы. Маршрут с целым числом:
path("posts/<int:post_id>/", views.post_detail, name="post_detail"),
Соответствующее представление принимает post_id вторым аргументом:
from django.shortcuts import render, get_object_or_404
from .models import Post
def post_detail(request, post_id):
# если записи с таким id нет — вернётся страница 404, а не ошибка 500
post = get_object_or_404(Post, pk=post_id)
return render(request, "blog/post_detail.html", {"post": post})
Функция get_object_or_404 избавляет от ручной проверки: нет объекта — корректный ответ 404 вместо необработанного исключения.
Если не получилось
- «TemplateDoesNotExist: blog/hello.html» — шаблон лежит не там. Проверьте путь:
blog/templates/blog/hello.html, с двойной вложенностьюblog. И убедитесь, что'blog'есть вINSTALLED_APPS, иначе Django не заглянет в его папку с шаблонами. - «Page not found (404)» на своём адресе — либо забыли
include("blog.urls")в главномmysite/urls.py, либо адрес в браузере не совпадает с маршрутом. Django чувствителен к завершающему слешу: маршрутposts/и запрос/postsбез слеша по умолчанию перенаправятся, но лучше набирать адрес точно. - «The view blog.views.hello didn't return an HttpResponse object. It returned None» — в функции нет
returnлибо ветка кода до него не дошла. Каждый путь выполнения должен что-то вернуть. - «no such table: blog_post» — не применили миграции. Выполните
python manage.py makemigrations blog, затемpython manage.py migrate. - «OperationalError» или пустой список, хотя записи создавали — возможно, записи добавлялись в другую базу или до миграций. Проверьте через
python manage.py shell, чтоPost.objects.count()возвращает ненулевое число.
Что дальше
Рабочий минимум готов: адрес, представление, шаблон, данные из базы. Дальше стоит посмотреть на:
- обработку форм — приём
POST-запросов и проверкаrequest.methodвнутри функции; - декоратор
@login_requiredизdjango.contrib.auth.decorators, чтобы закрыть представление от неавторизованных; - class-based views — когда однотипных представлений (список, деталь, создание) становится много, классы сокращают повторяющийся код. Это отдельная тема, здесь мы её не разворачиваем.
Проверять актуальный синтаксис path, доступные конвертеры URL и параметры render удобнее всего в официальной документации для вашей версии Django — она привязана к номеру релиза.
Проверка грамотности: Орфография, пунктуация, стиль — с объяснением каждой правки
Спросить за 10 ₽Источники: Django documentation — Writing views, Django documentation — URL dispatcher
Частые вопросы
У меня Django 4.2, а не 5.x — код сработает?
Чем функциональное представление отличается от представления на классе?
Обязательно ли класть шаблон в blog/templates/blog/, а не просто в templates/?
Почему получаю ошибку про None вместо HttpResponse?
Можно ли обойтись без render и всегда возвращать HttpResponse со строкой?
Как понять, какой аргумент придёт в представление из URL?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.