#38. DRF. Views, Routers

@api_view, APIView, ViewSets, Pagination, Routers

Веб насамперед - це Request-Response система.

class Request

Дока тут
Два нові парамери .data і .query_string

  • .data - дані, якщо запит POST, PUT або PATCH, аналог request.POST або request.FILES

  • .query_string - дані, якщо запит GET, аналог request.GET

І параметри .auth і .аuthenticators, які ми розглянемо на наступній лекції. Вона цілком про авторизацію іpermissions (доступи).

class Response

Дока тут

На відміну від класичної Django, відповіддю в REST-системі буде звичайна HTTP-відповідь, що містить набір даних, частіше за все усього JSON (але буває й ні).

Класична Django теж може повертати HTTP-відповідь і бути обробником REST-архітектури, але наявний пакет дуже спрощує ці процеси.

Для обробки такої відповіді є спеціальний об’єкт:

Response(data, status=None, template_name=None, headers=None, content_type=None)
  • data - дані,

  • status - код відповіді (200, 404, 503),

  • template_name - можливість вказати темплейт, якщо необхідно повернути сторінку, а не просто набір даних,

  • headers і content_type - заголовки і тип вмісту запиту.

@api_view

Дока тут

Для опису endpoint функціонально потрібно вказати декоратор api_view і методи, які він може приймати. Повертає все також об’єкт Request.

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


@api_view(['GET', 'POST'])
def snippet_list(request):
    """
    List all code snippets, or create a new snippet.
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Зверніть увагу, у пакеті REST фреймворку відразу є заготовлені об’єкти статусу для відповіді.

Якщо спробувати отримати доступ методом, який не дозволений, запит буде відхилено з відповіддю 405 Method not allowed

Для передачі параметрів використовуються аргументи функції. (Дуже схоже на звичайну Django в’ю)

@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

URLs для таких методів описуються так само, як і для стандартної Django в’ю.

from snippets.view import snippet_list, snippet_detail

urlpatterns = [
    path('snippets/', snippet_list),
    path('snippets/<int:pk>/', snippet_detail),
]

Відповідь на GET-запит у цьому випадку матиме такий вигляд:
http://127.0.0.1:8000/snippets/


HTTP/1.1 200 OK

[
  {
    "id": 1,
    "title": "",
    "code": "foo = \"bar\"\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  },
  {
    "id": 2,
    "title": "",
    "code": "print(\"hello, world\")\n",
    "linenos": false,
    "language": "python",
    "style": "friendly"
  }
]

Поля залежатимуть від моделі та серіалайзера відповідно.

Відповідь на POST запит (створення об’єкта):
http --form POST http://127.0.0.1:8000/snippets/ code="print(123)"

{
"id": 3,
"title": "",
"code": "print(123)",
"linenos": false,
"language": "python",
"стиль": "friendly"
}

View

Знайомимося з найдокладнішим сайтом з DRF класів тут

APIView

Дока тут

Ми можемо описати наші view через Class-Based View, для цього нам потрібно успадковуватися від APIView:

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status


class SnippetList(APIView):
    """
    List all snippets, or create a new snippet.
    """
    def get(self, request, format=None):
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

Винесемо отримання об’єкта в окремий метод:

class SnippetDetail(APIView):
    """
    Retrieve, update or delete a snippet instance.
    """
    def get_object(self, pk):
        try:
            return Snippet.objects.get(pk=pk)
        except Snippet.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    def put(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        snippet = self.get_object(pk)
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

URLs описуються так само, як і для Django Class-Based View:

from django.urls import path
from snippets import views


urlpatterns = [
    path('snippets/', SnippetList.as_view()),
    path('snippets/<int:pk>/', SnippetDetail.as_view()),
]

GenericView

Детальніше

За аналогією з класичною Django існують заздалегідь описані CRUD дії.

Як це працює?

Існує клас GenericAPIView, який успадковується від звичайного APIView.

У ньому описані такі поля як:

  • queryset зберігає кверисет;

  • serializer_class зберігає серіалайзер;

  • lookup_field = 'pk' - назва атрибута в моделі, який відповідатиме за PK;

  • lookup_url_kwarg = None - назва атрибута в запиті, який відповідатиме за pk;

  • filter_backends = api_settings.DEFAULT_FILTER_BACKENDS - фільтри запитів;

  • pagination_class = api_settings.DEFAULT_PAGINATION_CLASS - пагінація запитів.

І методи:

  • get_queryset - отримання кверисета;

  • get_object - отримання одного об’єкта;

  • get_serializer - отримання об’єкта серіалайзера;

  • get_serializer_class - отримання класу серіалайзера;

  • get_serializer_context - отримати контекст серіалайзера;

  • filter_queryset - відфільтрувати кверисет;

  • paginator - об’єкт пагінації;

  • paginate_queryset - пагінувати кверисет;

  • get_paginated_response - отримати пагіновану відповідь.

Такий клас не працює самостійно, тільки разом із певними міксинами

Mixins

У DRF існує 5 міксинів:

CreateModelMixin

class CreateModelMixin(object):
    """
    Create a model instance.
    """

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}

Розглянемо докладніше.

Це міксин, і без сторонніх класів цей функціонал працювати не буде.

Під час виклику методу create() ми припускаємо, що у нас був request.

Викликаємо метод get_serializer() з класу GenericAPIView для отримання об’єкта серіалайзера, зверніть увагу, що дані передаються через атрибут data, оскільки вони отримані від користувача. Перевіряємо дані на валідність (зверніть увагу увагу на атрибут raise_exception, якщо дані будуть не валідні, код одразу вилетить у traceback, а значить нам не потрібно окремо прописувати дії при не валідному серіалайзері), викликаємо метод perform_create, який просто зберігає серіалайзер (викликає create або update залежно від даних), отримує хедери, і повертає response з 201 кодом, створення успішно.

За аналогією ми можемо розглянути інші міксини.

class RetrieveModelMixin(object):
    """
    Retrieve a model instance.
    """

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

Зверніть увагу, метод називається retrieve() і всередині викликає метод get_object(), - це міксин одиночного об’єкта

class UpdateModelMixin(object):
    """
    Update a model instance.
    """

    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)

Методи update(), partial_update(), perform_update() потрібні для оновлення об’єкта, зверніть увагу на атрибут
partial. Пам’ятаєте різницю між PUT і PATCH?

class DestroyModelMixin(object):
    """
    Destroy a model instance.
    """

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()

Аналогічно для видалення методи destroy(), perform_destroy().

class ListModelMixin(object):
    """
    List a queryset.
    """

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

Метод list() отримує кверисет, далі намагається його пагінувати, якщо виходить, повертає сторінку, якщо ні - повертає цілу відповідь.

Важливо! У жодному з міксинів не було методів get(),post(),patch(),put() або delete(), чому?

Тому що їхній виклик перенесено в додаткові класи.

Generic класи

Ось так виглядають класи, які вже можна використовувати. Як це працює? Ці класи успадковують логіку роботи з даними
з необхідного міксину, загальні методи, які актуальні для будь-якої CRUD дії з GenericAPIView далі описуємо методи тих видів запитів, які ми хочемо обробляти, в яких просто викликаємо необхідний метод з міксину.

Переписуються методи create(), destroy() і т. д., а не get(), post()!

class CreateAPIView(mixins.CreateModelMixin,
                    GenericAPIView):
    """
    Concrete view for creating a model instance.
    """

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class ListAPIView(mixins.ListModelMixin,
                  GenericAPIView):
    """
    Concrete view for listing a queryset.
    """

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)
class RetrieveAPIView(mixins.RetrieveModelMixin,
                      GenericAPIView):
    """
    Concrete view for retrieving a model instance.
    """

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)
class DestroyAPIView(mixins.DestroyModelMixin,
                     GenericAPIView):
    """
    Concrete view for deleting a model instance.
    """

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)
class UpdateAPIView(mixins.UpdateModelMixin,
                    GenericAPIView):
    """
    Concrete view for updating a model instance.
    """

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)
class ListCreateAPIView(mixins.ListModelMixin,
                        mixins.CreateModelMixin,
                        GenericAPIView):
    """
    Concrete view for listing a queryset or creating a model instance.
    """

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)
class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
                            mixins.UpdateModelMixin,
                            GenericAPIView):
    """
    Concrete view for retrieving, updating a model instance.
    """

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)
class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
                             mixins.DestroyModelMixin,
                             GenericAPIView):
    """
    Concrete view for retrieving or deleting a model instance.
    """

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)
class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
                                   mixins.UpdateModelMixin,
                                   mixins.DestroyModelMixin,
                                   GenericAPIView):
    """
    Concrete view for retrieving, updating or deleting a model instance.
    """

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def patch(self, request, *args, **kwargs):
        return self.partial_update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

Припустимо, ми хочемо описати клас під час GET-запиту отримання списку коментарів, у яких є буква w, якщо в нас уже є
є серіалайзер і модель, а під час POST створення коментаря.

class CommentListView(ListCreateAPIView):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer

    def get_queryset(self):
        return super().get_queryset().filter(text__icontains='w')

ViewSet

Дока тут
Django REST фреймворк дозволяє об’єднати логіку для набору пов’язаних між собою views в одному класі, який називається ViewSet. В інших фреймворках ви також можете знайти концептуально схожі реалізації під назвою “Resources” або “Controllers”.

Клас ViewSet - це просто тип view на основі класу, який не надає жодних обробників методів, таких як .get() або .post(), а натомість надає дії, такі як .list() та .create().

Обробники методів для ViewSet прив’язуються до відповідних дій лише у момент завершення перегляду за допомогою методу .as_view().

Зазвичай, замість того, щоб явно реєструвати представлення у наборі представлень в urlconf, ви реєструєте набір представлень за допомогою класу Router, який автоматично визначає urlconf для вас.

Наприклад:

from django.contrib.auth.models import User
from django.shortcuts import get_object_or_404
from myapps.serializers import UserSerializer
from rest_framework import viewsets
from rest_framework.response import Response

class UserViewSet(viewsets.ViewSet):
    """
    A simple ViewSet for listing or retrieving users.
    """
    def list(self, request):
        queryset = User.objects.all()
        serializer = UserSerializer(queryset, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        queryset = User.objects.all()
        user = get_object_or_404(queryset, pk=pk)
        serializer = UserSerializer(user)
        return Response(serializer.data)

Різні дії за наявності та відсутності PK, при GET запиті.

Для опису URLs можна використовувати різний опис:

user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})

Хоч так ніхто й не робить, про це далі.

ModelViewSet і ReadOnlyModelViewSet

Дока тут

Об’єднуємо все, що ми вже знаємо.

І отримуємо клас ModelViewSet, він успадковується від GenericViewSet (ViewSet + GenericAPIView) і всіх 5 міксинів, а значить там описані методи list(), retrieve(), create(), update(), destroy(), perform_create(), perform_update() і т. д.

А отже, ми можемо описати сутність, яка приймає модель і серіалайзер, і вже може приймати будь-які типи запитів і виконувати будь-які CRUD дії. Ми можемо їх перевизначити, або дописати ще екшенів, все що нам може бути необхідно вже є.

Приклад:

class AccountViewSet(viewsets.ModelViewSet):
    """
    A simple ViewSet for viewing and editing accounts.
    """
    queryset = Account.objects.all()
    serializer_class = AccountSerializer
    permission_classes = [IsAccountAdminOrReadOnly]

Або якщо необхідний такий самий в’юсет тільки для отримання об’єктів, то:

class AccountViewSet(viewsets.ModelViewSet):
    """
    A simple ViewSet for viewing and editing the accounts
    associated with the user.
    """
    serializer_class = AccountSerializer
    permission_classes = [IsAccountAdminOrReadOnly]

    def get_queryset(self):
        return self.request.user.accounts.all()

Насправді, можна зібрати такий самий в’юсет для будь-яких дій, додаючи і прибираючи міксини.

Наприклад:

from rest_framework import mixins

class CreateListRetrieveViewSet(mixins.CreateModelMixin,
                                mixins.ListModelMixin,
                                mixins.RetrieveModelMixin,
                                viewsets.GenericViewSet):
    """
    A viewset that provides `retrieve`, `create`, and `list` actions.

    To use it, override the class and set the `.queryset` and
    `.serializer_class` attributes.
    """
    pass

Найчастіше використовуються звичайні ModelViewSet.

ReadOnlyModelViewSet

Клас ReadOnlyModelViewSet також успадковується від GenericAPIView. Як і у випадку з ModelViewSet, він також містить реалізації для різних дій, але на відміну від ModelViewSet надає лише дії “тільки для читання”, .list() та .retrieve().

class AccountViewSet(viewsets.ReadOnlyModelViewSet):
    """
    A simple ViewSet for viewing accounts.
    """
    queryset = Account.objects.all()
    serializer_class = AccountSerializer

Пагінація

Дока тут

Як ми пам’ятаємо, для дії list використовується пагінація. Як це працює?

Якщо у нас немає необхідності налаштовувати всі в’юсети окремо, то ми можемо вказати таке налаштування в settings.py:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 100
}

GET https://api.example.org/accounts/?limit=100&offset=400

Там ми можемо вказати тип класу пагінації та розмір однієї сторінки, і всі наші запити вже будуть пагіновані.

Також ми можемо створити класи пагінаторів, ґрунтуючись на нашій необхідності.

pagination.py

from rest_framework.pagination import PageNumberPagination


class LargeResultsSetPagination(PageNumberPagination):
    page_size = 1000
    page_size_query_param = 'page_size'
    max_page_size = 10000


class StandardResultsSetPagination(PageNumberPagination):
    page_size = 100
    page_size_query_param = 'page_size'
    max_page_size = 1000
REST_FRAMEWORK =  {

'DEFAULT_PAGINATION_CLASS': 'apps.api.pagination.StandardResultsSetPagination'

}

GET https://api.example.org/accounts/?page=4
Якщо потрібно вказати пагінатор у конкретного в’юсета, то можна це зробити прямо в атрибутах.

class BillingRecordsView(generics.ListAPIView):
    queryset = Billing.objects.all()
    serializer_class = BillingRecordsSerializer
    pagination_class = LargeResultsSetPagination

PageNumberPagination

Клас PageNumberPagination містить низку атрибутів, які можуть бути перевизначені для зміни стилю пагінації.
Щоб встановити ці атрибути, необхідно перевизначити клас PageNumberPagination, а потім включити свій власний клас пагінації, як зазначено вище.

  • django_paginator_class - Клас Django Paginator, який буде використовуватися. За замовчуванням це django.core.paginator.Paginator, що має бути добре для більшості випадків використання.
  • page_size - Числове значення, що вказує розмір сторінки. Якщо встановлено, воно скасовує налаштування PAGE_SIZE. За замовчуванням має те саме значення, що й ключ налаштування PAGE_SIZE.
    page_query_param - Рядкове значення, що вказує ім’я параметра запиту, який буде використовуватися для управління пагінацією.
  • page_size_query_param - Якщо встановлено, це строкове значення, що вказує ім’я параметра запиту, який дає змогу клієнту встановлювати розмір сторінки на основі кожного запиту. За замовчуванням None, що означає, що клієнт не може контролювати розмір запитуваної сторінки.
  • max_page_size - Якщо встановлено, це числове значення, що вказує на максимально допустимий розмір запитуваної сторінки. Цей атрибут дійсний, тільки якщо page_size_query_param також встановлено.
  • last_page_strings - Список або кортеж строкових значень, що вказують на значення, які можуть бути використані з page_query_param для запиту останньої сторінки в наборі. За замовчуванням (‘last’,)
  • template - Ім’я шаблону для використання під час відображення елементів керування пагінацією в переглянутому API. Може бути перевизначено для зміни стилю візуалізації або встановлено в None для повного вимкнення HTML елементів керування пагінацією. За замовчуванням використовується “rest_framework/pagination/numbers.html”

Декоратор @action

Дока тут

Що робити, якщо вам потрібна додаткова дія, пов’язана з деталями вашого в’ю, але жоден із крудів не підходить? Тут можна використовувати декоратор @action, щоб описати нову дію в цьому ж в’юсеті.

from django.contrib.auth.models import User
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from myapp.serializers import UserSerializer, PasswordSerializer

class UserViewSet(viewsets.ModelViewSet):
    """
    A viewset that provides the standard actions
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

    @action(detail=True, methods=['post'])
    def set_password(self, request, pk=None):
        user = self.get_object()
        serializer = PasswordSerializer(data=request.data)
        if serializer.is_valid():
            user.set_password(serializer.validated_data['password'])
            user.save()
            return Response({'status': 'password set'})
        else:
            return Response(serializer.errors,
                            status=status.HTTP_400_BAD_REQUEST)

    @action(detail=False)
    def recent_users(self, request):
        recent_users = User.objects.all().order_by('-last_login')

        page = self.paginate_queryset(recent_users)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(recent_users, many=True)
        return Response(serializer.data)

Приймає два основні параметри: detail описує, чи повинен цей action приймати PK (дія над усіма об’єктами або над одним конкретним), і methods - список HTTP методів, на які має спрацьовувати action.

Є й інші, наприклад, класи permissions або ім’я.

Роутери

Дока тут

Router - це автоматичний генератор URLs для в’юсетів.

from rest_framework import routers

router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
urlpatterns = router.urls

У методі register приймає два параметри, на якому слові засновувати URLs і для якого в’юсета.

Якщо у в’юсета немає параметра queryset, то потрібно вказати поле basename, якщо ні, то автоматично буде використано ім’я моделі маленькими літерами.

URLs будуть згенеровані автоматично, і їм будуть автоматично присвоєні імена:

URL pattern: ^users/$ Name: 'user-list'
URL pattern: ^users/{pk}/$ Name: 'user-detail'
URL pattern: ^accounts/$ Name: 'account-list'
URL pattern: ^accounts/{pk}/$ Name: 'account-detail'

Найчастіше роутери до URLs додаються ось такими способами:

urlpatterns = [
    path('forgot-password', ForgotPasswordFormView.as_view()),
    path('api/', include(router.urls)),
]

SimpleRouter

Цей router містить маршрути для стандартного набору дій list, create, retrieve, update, partial_update та destroy. Набір viewset може також позначати додаткові методи для маршрутизації за допомогою декоратора @action.

router = SimpleRouter()

DefaultRouter

Цей router схожий на SimpleRouter, як описано вище, але додатково включає API root view за замовчуванням, яке повертає відповідь, що містить гіперпосилання на всі views списку. Він також генерує маршрути для необов’язкових суфіксів формату .json.

Роутинг екстра екшенів

Припустимо, є такий екстра екшен:

from rest_framework.decorators import action


class UserViewSet(ModelViewSet):
    ...

    @action(methods=['post'], detail=True)
    def set_password(self, request, pk=None):
        ...

Роутер автоматично згенерує URL ^users/{pk}/set_password/$ та ім’я user-set-password.

Клас SimpleRouter може приймати параметр trailing_slash True або False, за дефолтом True, тому всі API, мають приймати URLs, що закінчуються на /, якщо вказати явно, то прийматиме все без /.

Домашнє завдання:

Продовжуємо стрворювати API для магазину із модуля Django

  1. Повний CRUD для User, Product, Order, мають працювати всі види запитів через postman.

  2. При створені product додавати в кінці назви символ “!”.

  3. *Для методу GET user, додати опціональний параметр amount,

  • якщо він вказаний і там число, то відображати тільки юзерів, у яких wallet більший або дорівнює зазначеному amount
  1. Для методу GET Product, додати опціональний параметр name, для фільтрації товарів, у назви яких частково збігаються із зазначеним параметром.

  2. Додати окремий action, щоб отримувати об’єкт user з полем orders, в якому будуть лежати id всіх замовлень.

Література

  1. DRF Quickstart