- Роутеры: SimpleRouter и DefaultRouter
- Декоратор @action
- Метод get_queryset
- Собственный класс роутера (custom router)
- Express.js Маршрутизация
- Что такое маршрутизация в Express.js:
- Работа с маршрутизацией в Express.js:
- Главная страница
- Дополнительные параметры URL:
- Делаем навигацию
- Правильный способ
- Используем навигацию как фильтр
- Чиним роутер
- Подключаем фильтр
Роутеры: SimpleRouter и DefaultRouter
На предыдущем занятии мы научились использовать классы вьюсетов для комплексной работы с данными таблиц БД, не прописывая отдельные представления для каждого API-маршрута. Но один вопрос остался практически нераскрытым – это работа с маршрутизатором.
Если перейти на страницу официальной документации по Django REST Framework: 5
то мы увидим два стандартных класса для определения роутеров:
SimpleRouter и DefaultRouter
Они практически идентичны по функционалу, единственное отличие – это то, что DefaultRouter дополнительно создает корневой маршрут:
где отображает все связанные с роутером дочерние маршруты. В нашем случае будет получен следующий JSON-ответ:
так как был зарегистрирован префикс women для данного роутера. А также позволяет использовать параметр format для API-запросов, например, так:
получим «чистый» JSON-ответ, а не HTML-документ для браузера.
Давайте выведем коллекцию router.urls в консоль и посмотрим, из каких маршрутов она состоит:
Увидим следующую информацию:
router.register(r'women2', WomenViewSet)
то имена маршрутов останутся прежними. Но, при необходимости, мы можем поменять и этот префикс в именах с помощью специального параметра basename при регистрации вьюсета, например:
router.register(r'women', WomenViewSet, basename='men')
Видим, что теперь имена начинаются с men. Кстати, этот параметр (basename) обязателен, если во вьюсете не определен атрибут queryset.
Декоратор @action
Итак, класс роутера, фактически, формирует список URL-маршрутов и связывает их с определенным вьюсетом. Но что делать, если этих маршрутов по умолчанию недостаточно и нужно дополнительно определить свой для того же вьюсета? Разработчики Django REST Framework для этого определили специальный декоратор @action, с помощью которого можно через методы создавать новые, дополнительные маршруты в рамках одного вьюсета. Давайте, в качестве простого примера, создадим маршрут для отображения списка категорий. Определим в классе WomenViewSet, следующий метод:
class WomenViewSet(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet): queryset = Women.objects.all() serializer_class = WomenSerializer @action(methods=['get'], detail=False) def category(self, request): cats = Category.objects.all() return Response({'cats': [c.name for c in cats]})
Мы здесь через декоратор action указываем список разрешенных методов (в данном случае – это один метод GET), а также тип маршрута. Если указан параметр detail=False, то ожидается работа со списком и маршрут не будет использовать параметр pk – идентификатор записи. Далее следует метод класса с именем category и двумя обязательными параметрами: self и request. Внутри этого метода читаем все записи из таблицы category и формируем самостоятельно JSON-ответ, так как сериализатор класса для этих данных уже не подходит. В результате, роутер сформирует дополнительный URL-адрес вида: http://127.0.0.1:8000/api/v1/women/category/ Наберем его в браузере и увидим на выходе JSON-строку с именами категорий. Если же нам нужно работать с конкретной записью, то параметр detail=True и метод category должен определять еще один параметр pk:
@action(methods=['get'], detail=True) def category(self, request, pk=None): cats = Category.objects.get(pk=pk) return Response({'cats': cats.name})
Теперь в браузере прежний маршрут будет недоступен, а новый имеем вид: http://127.0.0.1:8000/api/v1/women/1/category/ Обратите внимание, мы здесь идентификатор указываем до фрагмента category, а не после.
Метод get_queryset
Иногда при разработке API нам нужно по URL-запросу выбирать из таблицы БД не все записи, а определенные, иногда по довольно сложным условиям с группировкой и прочим. Как это можно реализовать в рамках нашего проекта? Для этого в каждом классе представления DRF можно переопределить специальный метод get_queryset(), который должен возвращать список отобранных записей. Например, если его записать в виде:
def get_queryset(self): return Women.objects.all()[:3]
то по запросу будут возвращаться только первые три отобранные записи. При этом сам атрибут queryset в классе WomenViewSet можно убрать. Но, так как мы используем роутер, то при регистрации обязательно нужно прописать параметр basename:
router.register(r'women', WomenViewSet, basename='women')
Теперь, при GET-запросе: http://127.0.0.1:8000/api/v1/women/ мы увидим не все записи, а только первые три. Однако, при попытке обратиться к конкретной записи: http://127.0.0.1:8000/api/v1/women/1/ получим ошибку. Дело в том, что наш метод get_queryset() всегда выдает список. Поправим это:
def get_queryset(self): pk = self.kwargs.get("pk") if not pk: return Women.objects.all()[:3] return Women.objects.filter(pk=pk)
Теперь, если появляется параметр pk, то мы выдает список из одной конкретной записи, а иначе – список из первых трех. Проверяем, все работает, как и прежде.
Собственный класс роутера (custom router)
В заключение этого занятия хочу показать, как можно создавать свои собственные классы роутеров. Требуется это относительно редко, но знать о такой возможности нужно. В качестве примера я непосредственно в файле drfsite/urls.py пропишу новый класс роутера. Конечно, в реальном проекте все определения классов нужно делать в соответствующих файлах. Например, можно создать файл routers.py в пакете women, а затем, импортировать его в модуль urls.py. Но сейчас в учебных целях, думаю, будет достаточно просто определить класс прямо здесь. Он будет следующим:
class MyCustomRouter(routers.SimpleRouter): routes = [ routers.Route(url=r'^$', mapping={'get': 'list'}, name='-list', detail=False, initkwargs={'suffix': 'List'}), routers.Route(url=r'^/$', mapping={'get': 'retrieve'}, name='-detail', detail=True, initkwargs={'suffix': 'Detail'}) ]
- mapping – связка типа запроса (GET, POST и т.п.) и соответствующего метода вьюсета;
- name – название маршрута;
- detail – список или отдельная запись;
- initkwargs – дополнительные аргументы для коллекции kwargs, которые передаются конкретному представлению при срабатывании маршрута.
Express.js Маршрутизация
В этой части учебника по Express.js мы рассмотрим что такое роутинг или маршрутизация на этом фреймворки, и на мой взгляд это самое важное что стоит знать про это.
Что такое маршрутизация в Express.js:
Маршрутизация это когда зависимо от URL запроса вам даётся какая-то страница или отправляться данные определённые данные.
Обычно в URL записывается путь до файла, который вам нужно открыть, но сейчас делают так, чтобы зависимо от него, вам просто отправлялся шаблон или JSON данные, ещё влияет тип запроса, GET или POST, поэтому это тоже стоит учитывать.
Работа с маршрутизацией в Express.js:
Для работы создадим папку «routes», если её у вас нет, создадим в ней файл «index.js» и вот что в нём пишем:
res . send ( «
Главная страница
» ) ;
В начале мы тут импортируем сам Express, потом из него создаём маршрутизацию, и делаем ответ на первый наш GET запрос, который отправляем благодаря методу res.send() , как видите, мы в ответ клиенту отправили заголовок «Главная страница».
Также вы можете заметить, что создаём мы проверку GET запроса благодаря router.get() , первым параметром он принимает какому URL должен быть запрос, чтобы отправить данные клиенту, второе, это функция, которая ещё принимает два параметра, первый содержит дополнительные данные, которые получает сервер от пользователя, а второе, это сам ответ, благодаря второму мы и формируем ответ клиенту.
Теперь создаём или заходим файл «app.js», внутри него прописываем что-то типа этого:
В начале мы импортируем файл библиотеку Express.js, потом наш маршрут, и создаём приложение Express, назначаем маршрут, делаем это благодаря методу app.use() (Что это такое в следующей части), туда передаём корневой путь и наш роутинг, который создали выше и запускаем слушатель сервера на порту 3000, но это уже не важно.
Благодаря этому, когда зайдём на локальный сервер увидим наш заголовок, вот что получается в браузере:
Как видите всё сработало, и теперь вы можете таким образом управлять путями и выводить что вам нужно.
Также есть и другие методы, для других типов запросов, кроме GET, вот они все:
- router.get() — GET запрос;
- router.post() — POST запрос;
- router.put() — PUT запрос для создания элементов, похож на POST;
- router.delete() — DELETE запрос для удаления элементов, похож на POST;
Дополнительные параметры URL:
Ещё вы можете встраивать какие-то параметры благодаря знаку двоеточие, вот как это примерно будет выглядеть:
Делаем навигацию
теперь можно пойти в базовый шаблон __layout.twig и добавить там цикл по типам:
lang="en"> class="navbar navbar-expand-lg navbar-light bg-light"> class="container"> class="navbar-brand" href="#"> class="fas fa-meteor"> class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> class="navbar-toggler-icon"> class="collapse navbar-collapse" id="navbarNav"> class="navbar-nav"> class="nav-item"> class="nav-link active" aria-current="page" href="/">Главная for type in types %> class="nav-item"> class="nav-link" href="#"> type.type|title >> endfor %> class="container pt-3 pb-3"> block content %> endblock %>
то есть значения из базы подцепились, правда ссылки пока еще не работают.
Чем плох этот способ? С точки зрения шаблона – все прекрасно. А вот с точки зрения кода, не очень. У нас index.php – это входная точка приложения, которая управляет высокими материями, роутером, подключением пакетов, создания соединения с базой данных, активация генератора шаблонов, ну и как бы все это связывает между собой.
И вот эта попытка сделать запрос к БД и передать что-то в twig, выглядит как будто мы на концерте симфонической музыке дуем в свисток. В общем, не уместно, плюс невозможно тестировать.
Правильный способ
Значит, более корректный подход — это создать базовый контроллер для нашего приложения (не фреймворка – фреймворк, в идеале должен быть не зависим от темы проекта) и уже все остальные контроллеры наследовать от него.
Давайте это сделаем. Создаем файлик, я его назову BaseSpaceTwigController.php и загоню код который был в index.php, но с небольшими правками
ну и заменяем везде на TwigBaseController на BaseSpaceTwigController
но архитектурно более грамотно.
Используем навигацию как фильтр
Сейчас я хочу сделать так, чтобы тыкая на элемент навигации срабатывал один и тот же контроллер. Но чтобы он понимал, что я хочу видеть объекты только какого-то специфического типа.
Для этого надо чтобы к ссылке, ведущей на главную страницу добавились параметры запроса. Делается это следующим образом. Идем в __layout.twig и добавляем в href у элементов навигации параметры:
for type in types %> class="nav-item"> type.type >>" --> class="nav-link" href="/?type= type.type >>"> type.type|title >> endfor %>
то есть у нас в запросе появляется часть ответственная за параметры, но роутер почему-то перестает распознавать путь
Чтобы понять, давайте добавим вывод $_SERVER[«REQUEST_URI»] в роутере
class Router // . public function get_or_default($default_controller) $url = $_SERVER["REQUEST_URI"]; print_r($url); // добавил вывод // . > // . >
то есть PHP не отделяет за нас сам адрес, от параметров адреса. К счастью имеется функция которая умеет это делать. Зовется parse_url – знает, как из строки url вытаскивать всякие отдельные куски, на вроде схемы, адреса сайта, полного адреса и прочих элементов, которых на самом деле не так уж и мало. Проверим как она работает:
public function get_or_default($default_controller) $url = $_SERVER["REQUEST_URI"]; $path = parse_url($url, PHP_URL_PATH); // вытаскиваем адрес echo $path; // выводим // . >
Но помимо самого адреса нам нужны еще и параметры.
А с параметрами все проще. Набор параметров в адресной строке называется параметрами GET запроса и доступны они через специальную переменную $_GET , давайте глянем:
public function get_or_default($default_controller) $url = $_SERVER["REQUEST_URI"]; $path = parse_url($url, PHP_URL_PATH); echo $path; echo ""
; // чтобы красивее выводил print_r($_GET); // выведем содержимое $_GET echo "
"; // . >
мы можем через & добавить еще пару параметров, например, http://localhost:9007/?type=галактика&sort=123&my_array[0]=c&my_array[1]=b и глянуть что произойдет:
то есть этот $_GET хранит в удобном виде параметры из адресной строки.
Чиним роутер
Так давайте сначала починим роутер, чтобы он игнорил параметры и тестировал на совпадение только сам адрес, вот так:
Подключаем фильтр
А теперь идем в MainController и правим там: