Django — роутинг запросов в urls.py

У каждой страницы в интернете есть уникальный веб-адрес, URL (это сокращение от английского Uniform Resource Locator, «унифицированный указатель ресурса»).URL Яндекса — https://yandex.ru/. URL Яндекс.Практикума — https://praktikum.yandex.ru/. На сайтах обычно есть множество страниц, и у каждой из них есть собственный URL. Например, https://yandex.ru/images/ на Яндексе или https://praktikum.yandex.ru/backend-developer/ на Практикуме. Адреса всех страниц начинаются с названия протокола (http или https) и имени домена. Например, адреса всех страниц Яндекса начинаются с https://yandex.ru/, где имя домена — строка yandex.ru.Когда разговор идёт о страницах определённого сайта, подразумевается, что говорят о страницах в пределах одного домена, так что не обязательно каждый раз упоминать доменное имя. URL, записанные без указания протокола и головного домена (backend-developer/ вместо https://praktikum.yandex.ru/backend-developer/), называют относительными. Относительным адресом главной страницы любого сайта будет пустая строка. Это немного неожиданно, но логично: ведь в адресе главной страницы после имени домена ничего не пишется. Полные адреса (https://praktikum.yandex.ru/backend-developer/) называются абсолютными.

Обработка запросов в Django

При создании проекта разработчик сам придумывает адреса, которые будут доступны на сайте. Эти адреса хранятся в коде; при запросе к проекту Django перебирает список адресов проекта, сравнивая их с запрошенным.Если запрошенный URL есть в списке, то вызывается связанная с этим адресом функция, которая обрабатывает запрос и делает всё необходимое: генерирует и отправляет пользователю запрошенную страницу, записывает данные в БД или делает что-то ещё хорошее.Если же запрошенный URL не обнаружился в списке заготовленных адресов, пользователю отправляется страница с сообщением об ошибке: 404, Page Not Found, «вы ошиблись адресом!».

Проектирование адресов

В качестве адресов могут выступать любые строки, но обычно используют английские слова в нижнем регистре, разделённые знаками - или _. Будет здорово, если по адресу пользователь сможет понять, какая информация содержится на странице.Заканчивайте адреса символом /. Это соответствует соглашению о наименовании адресов Django. Списки адресов в Django хранят в переменной urlpatterns в файлах urls.py. Основной файл urls.py лежит в главной папке проекта.Адреса в списке указывают в виде относительных URL.В проекте «Анфиса» будут такие адреса:

  1. Главная страница: '', пустая строка.
  2. Список сортов мороженого: 'ice_cream/'.
  3. Подробная информация о мороженом номер 1: 'ice_cream/1/'.
  4. Подробная информация о мороженом номер 2: 'ice_cream/2/'.
  5. И так далее, для всех сортов мороженого в базе данных.

Однако перечислить в этом списке все страницы с сортами мороженого не получится: администратор проекта может добавлять или удалять такие страницы, в результате придётся каждый раз вручную править список адресов.Эта проблема решается просто: вместо конкретных URL можно применять маски адресов, в которых часть адреса заменена переменной (имя переменной указывается в треугольных скобках <>).

...
'ice_cream/<pk>/',
# Такой адрес совпадёт с адресами 
# ice_cream/1/
# ice_cream/48/
# и даже с адресом ice_cream/plombir/
... 

Например, при запросе к URL ice_cream/859/ в переменную pk будет записано значение 859. Затем в функции-обработчике эту переменную можно использовать для получения запрошенного мороженого из базы данных.Таких переменных в шаблоне адреса может быть и несколько. Например, страница публикации определённого пользователя в блоге может содержать две переменные — username автора и id поста: users/<username>/<post_id>/.

Связь urls с обработчиком

После того как Django найдёт совпадение запрошенного URL с шаблоном адреса из списка, должен быть вызван обработчик — функция или класс, в которых после обработки запроса будет подготовлен ответ.Начиная с версии Django 2 для связи URL и обработчика применяется функция path(). Она принимает обязательные параметры path('route', view):

  • route — шаблон обрабатываемого адреса, образец, с которым сравнивается полученный запрос;
  • view — функция-обработчик: если запрошенный URL совпадает с route, вызов будет перенаправлен в указанную view-функцию (view-функции в Django хранят в файле views.py).

В проекте может быть много обрабатываемых путей, в них описаны все доступные адреса проекта. Все path() хранят в файле urls.py в виде списка (обычно его называют urlpatterns).

# Файл urls.py

...

urlpatterns = [
    ...
    # Главная страница:
    # если получен запрос без относительного адреса
    # (то есть это запрос к имени домена, например anfisa.com),
    # будет вызвана функция index() из файла views.py
    path('', views.index),

    # Страница со списком сортов мороженого:
    # если получен запрос ice_cream/,
    # будет вызвана функция ice_cream_list() из файла views.py
    path('ice_cream/', views.ice_cream_list),

    # Отдельная страница с информацией о сорте мороженого
    # если получен запрос ice_cream/<любой_набор_символов>,
    # будет вызвана функция ice_cream_detail() из файла views.py
    path('ice_cream/<pk>/', views.ice_cream_detail),
] 

Функциям-обработчикам нужно давать понятные имена. Из названия функции должно быть понятно, какой путь она обрабатывает.При получении запроса Django проходит по списку urlpatterns сверху вниз, пока не найдёт совпадение запрошенного адреса с шаблоном адреса. При обнаружении совпадения будет вызвана соответствующая view-функция.

Разделение адресов по приложениям. Функция include()

Обычно Django-проект состоит из нескольких приложений, и каждое приложение обрабатывает свою часть запросов.Например, в проекте «Анфиса для друзей» помимо ice_cream могло бы быть ещё несколько приложений; это могут быть приложения news (отвечает за ленту новостей о мороженом), blog (дневник разработчиков проекта) и forum (площадка для общения друзей Анфисы).Адреса проекта лучше спроектировать так, чтобы они соответствовали приложениям:

  • адреса ice_cream/... будут обрабатываться в приложении ice_cream,
  • адреса news/... — в приложении news.

Аналогично и с остальными приложениями.Хранить все шаблоны адресов всех приложений в одном файле будет неудобно: в какой-то момент список шаблонов разрастётся и станет нечитаемым. Хорошей стратегией будет разделить список urlpatterns на части, в соответствии с приложениями, и хранить эти части в директориях приложений.Django даёт такую возможность: в каждом приложении можно создать собственный файл urls.py, в котором будут перечислены адреса, обрабатываемые именно этим приложением, а в корневом urls.py будут ссылки на эти файлы.Для создания таких ссылок используется функция include() (англ. «включить», «встроить»).Запросы к главной странице проекта «Анфиса для друзей» будем обрабатывать в urls.py приложения ice_cream:

# anfisa/urls.py (главный файл url проекта)
# По умолчанию в проект Django подключена система администрирования 
from django.contrib import admin
# Функция include позволит использовать path() из других файлов.
# Импортируем!
from django.urls import include, path 

urlpatterns = [    
    # Дорогой Джанго, если запрошена главная страница (''),
    # перейди в файл urls приложения ice_cream и проверь там все пути
    path('', include('ice_cream.urls')),
    # Встроенная админка в Django подключена по этому адресу «из коробки»
    path('admin/', admin.site.urls),
] 

А файл urls.py приложения ice_cream будет выглядеть так:

# ice_cream/urls.py
from django.urls import path
from . import views

urlpatterns = [
    # Главная страница
    path('', views.index),
    # Страница со списком сортов мороженого
    path('ice_cream/', views.ice_cream_list),
    # Отдельная страница с информацией о сорте мороженого
    path('ice_cream/<pk>/', views.ice_cream_detail),
] 

В результате таких действий проект будет готов работать с адресами '''ice_cream/' и 'ice_cream/<pk>/'.

Конвертеры пути

В адресе ice_cream/<pk>/ в аргументе pk ожидается число, но пользователь может по ошибке или из любопытства вписать туда любую строчку: например, отправить запрос ice_cream/eskimo/ вместо ice_cream/15/. Это нарушит нам весь фэншуй.Для профилактики такого несознательного поведения используют конвертеры пути (англ. path converters): перед именем переменной указывается тип ожидаемых данных: ice_cream/<int:pk>/.Если в аргументе будут данные, не соответствующие конвертеру, Django будет считать, что шаблон адреса не совпадает с запросом, и продолжит поиск совпадений по urlpatterns.Теперь ни один пользователь не сможет накормить обработчик неудобоваримой переменной.Вот список наиболее востребованных конвертеров:

  • str — ожидает любую непустую строку, включая разделитель пути '/'. Если в параметрах пути конвертер не указан явно, то именно конвертер str будет применён по умолчанию: шаблон адреса user/<username>/ идентичен шаблону user/<str:username>/.
  • Конвертер int ожидает в качестве значения ноль или любое целое положительное число. Синтаксис: ice_cream/<int:pk>/.
  • Конвертер slug ожидает строку из букв и цифр, входящих в стандарт ASCII, а также символов - и _.Обычно слаг используют для создания человекочитаемых URL. При сравнении двух URL, в одном из которых аргумент — число, а в другом — slug, станет видна разница: по адресу group/cats/ безусловно ясно, что страница про котиков. А из адреса group/1/ не понятно вообще ничего, сплошь туманная неопределённость.Адреса с применением slug любят и люди, и поисковые роботы.

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

Конфликты адресов: кто выше — тот и главный

Django обрабатывает список urlpatterns сверху вниз и вызывает обработчик из первого же path(), в котором шаблон адреса совпадёт с запросом. Поэтому могут возникать коллизии.Например, при описанной ниже структуре списка view-функция just_page() никогда не будет вызвана: первый шаблон будет перехватывать все запросы с числовыми аргументами, в том числе с аргументом 1:


urlpatterns = [
    path('page/<int:pk>/', views.material_by_id),    
    path('page/1/', views.just_page),
] 

При планировании структуры адресов эту особенность стоит учитывать. Многих конфликтов можно избежать, если следовать правилу «адреса с частными случаями ставим выше, более общие шаблоны — ниже»: адрес page/1/ — это частный случай шаблона page/<int:pk>/, значит, page/1/ надо поставить ближе к началу списка.





Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: