В прошлом уроке мы написали первое представление и подключили его к маршруту. Сейчас разберём эту механику подробнее — она будет работать одинаково во всех последующих уроках.
Когда браузер отправляет запрос на /films/, Django делает следующее:
- Берёт путь из URL — всё, что идёт после домена
- Идёт в корневой
urls.pyи перебирает списокurlpatternsсверху вниз - Находит первое совпадение и передаёт запрос в соответствующее представление
- Представление возвращает ответ — Django отдаёт его браузеру
Если ни один маршрут не совпал — Django возвращает ответ 404 Not Found.
Функция представления (FBV — Function Based View) — это обычная Python-функция, которая:
- принимает объект
requestпервым аргументом - возвращает объект
HttpResponse(или его наследника)
from django.http import HttpResponse
def index(request):
return HttpResponse('Главная страница')Объект request содержит всю информацию о входящем запросе. Самые нужные атрибуты сейчас:
| Атрибут | Что содержит |
|---|---|
request.method |
Метод запроса: 'GET' или 'POST' |
request.GET |
Параметры из строки запроса (?page=2) |
request.POST |
Данные из тела POST-запроса |
request.user |
Текущий пользователь (разберём в модуле 7) |
request.GET — это словарь (точнее, QueryDict) с параметрами из строки запроса. Значение можно получить как request.GET.get('page') или request.GET['page']. Первый вариант безопаснее: если параметра нет, вернёт None, а не выбросит исключение.
Связь с прошлым курсом. В FastAPI параметры запроса описывались через аргументы функции с аннотациями типов, и фреймворк парсил их автоматически. В Django всё то же самое лежит в
request.GETиrequest.POST— просто в виде словарей, без автоматической валидации. Валидацию мы добавим в модуле 5, когда дойдём до форм.
Статический маршрут — это фиксированная строка пути. Мы уже писали такие в первом уроке:
# films/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('about/', views.about, name='about'),
path('films/', views.film_list, name='film_list'),
]Функция path() принимает три аргумента:
- маршрут — строка пути, без ведущего слеша
- представление — функция, которая обработает запрос
- name — имя маршрута для обращения к нему из кода и шаблонов (разберём подробнее в модуле 2)
Добавим в проект список фильмов. Сначала — заглушку, без базы данных:
# films/views.py
from django.http import HttpResponse
def index(request):
return HttpResponse('Добро пожаловать на сайт фильмов!')
def about(request):
return HttpResponse('Сайт-каталог фильмов. Здесь собраны лучшие фильмы всех времён.')
def film_list(request):
return HttpResponse('Список фильмов — скоро здесь будет каталог.')Страница со списком фильмов — это статический маршрут: один URL, одна страница. Но нам нужна и страница отдельного фильма: /films/1/, /films/2/, /films/42/. Создавать отдельный маршрут для каждого фильма невозможно — их сотни.
Решение — динамический URL с параметром:
# films/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('about/', views.about, name='about'),
path('films/', views.film_list, name='film_list'),
path('films/<int:film_id>/', views.film_detail, name='film_detail'),
]Угловые скобки <int:film_id> — это захват параметра из URL. Django извлечёт число из пути и передаст его в представление как аргумент film_id.
Представление должно принять этот аргумент явно:
def film_detail(request, film_id):
return HttpResponse(f'Страница фильма с id={film_id}')Теперь запрос на /films/7/ вызовет film_detail(request, film_id=7).
Часть int: перед именем параметра — это конвертер. Он делает две вещи одновременно: проверяет, что значение в URL подходит под тип, и преобразует строку в нужный Python-тип.
Встроенные конвертеры:
| Конвертер | Что принимает | Python-тип |
|---|---|---|
int |
Целое положительное число | int |
str |
Любая строка без слеша (по умолчанию) | str |
slug |
Строка из букв, цифр, дефисов и подчёркиваний | str |
uuid |
UUID в стандартном формате | uuid.UUID |
path |
Любая строка, включая слеши | str |
Примеры:
urlpatterns = [
# /films/42/ → film_id=42 (int)
path('films/<int:film_id>/', views.film_detail, name='film_detail'),
# /films/the-godfather/ → slug='the-godfather' (str)
path('films/<slug:slug>/', views.film_by_slug, name='film_by_slug'),
]Если URL не соответствует конвертеру — Django автоматически вернёт 404. Например, запрос на /films/abc/ при конвертере int не совпадёт с маршрутом вообще.
Конвертер
slugнам понадобится в модуле 3, когда мы добавим полеSlugFieldк моделиFilmи научим Django строить красивые URL вида/films/the-godfather/вместо/films/42/.
Если написать <film_id> без конвертера, Django применит str по умолчанию — параметр будет строкой.
urlpatterns = [
path('films/<film_id>/', views.film_detail, name='film_detail'),
]Запрос /films/abc/ тоже совпадёт с маршрутом, и в представлении придётся вручную проверять и преобразовывать значение.
В URL можно захватывать несколько параметров одновременно. Пример — маршрут для фильмов, отфильтрованных по году:
# /films/2024/3/ → фильмы 2024 года, страница 3
path('films/<int:year>/<int:page>/', views.films_by_year, name='films_by_year'),def films_by_year(request, year, page):
return HttpResponse(f'Фильмы {year} года, страница {page}')Django перебирает urlpatterns сверху вниз и останавливается на первом совпадении. Если поставить более общий маршрут раньше специфичного, специфичный никогда не сработает:
# Неправильно: маршрут со строкой перехватит 'new' раньше, чем до него дойдёт очередь
urlpatterns = [
path('films/<str:slug>/', views.film_by_slug),
path('films/new/', views.film_create), # сюда никогда не дойдёт
]
# Правильно: специфичные маршруты — выше
urlpatterns = [
path('films/new/', views.film_create),
path('films/<str:slug>/', views.film_by_slug),
]По умолчанию Django ожидает слеш в конце пути: films/, а не films. Если пользователь зайдёт на /films без слеша — Django автоматически перенаправит его на /films/. Это поведение регулируется настройкой APPEND_SLASH = True в settings.py и менять его без необходимости не стоит.
# films/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
path('about/', views.about, name='about'),
path('films/', views.film_list, name='film_list'),
path('films/<int:film_id>/', views.film_detail, name='film_detail'),
]# films/views.py
from django.http import HttpResponse
def index(request):
return HttpResponse('Добро пожаловать на сайт фильмов!')
def about(request):
return HttpResponse('Сайт-каталог фильмов. Здесь собраны лучшие фильмы всех времён.')
def film_list(request):
return HttpResponse('Список фильмов — скоро здесь будет каталог.')
def film_detail(request, film_id):
return HttpResponse(f'Страница фильма с id={film_id}')- Что произойдёт, если ни один маршрут в
urlpatternsне совпадёт с запрошенным URL? - Зачем нужен конвертер типа в динамическом URL? Что будет, если написать
<film_id>без конвертера? - Есть два маршрута:
path('films/<str:slug>/', ...)иpath('films/new/', ...). Какой из них нужно поставить первым и почему? - Какой атрибут объекта
requestсодержит параметры из строки запроса, например?page=2? - Чем конвертер
slugотличается отstr?
Тип: расширь проект
Добавь в проект страницу режиссёра.
Требования:
- Представление называется
director_detailи находится вfilms/views.py - Оно принимает
director_idиз URL как целое число - Доступно по адресу
/directors/<director_id>/ - Возвращает строку вида:
'Страница режиссёра с id=5'(число подставляется из URL) - Маршрут имеет имя
director_detail
Проверь, что:
/directors/1/работает и возвращает правильный текст/directors/abc/возвращает 404