#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
-
Повний CRUD для User, Product, Order, мають працювати всі види запитів через
postman. -
При створені product додавати в кінці назви символ “!”.
-
*Для методу
GETuser, додати опціональний параметрamount,
- якщо він вказаний і там число, то відображати тільки юзерів, у яких wallet більший або дорівнює зазначеному amount
-
Для методу
GETProduct, додати опціональний параметрname, для фільтрації товарів, у назви яких частково збігаються із зазначеним параметром. -
Додати окремий action, щоб отримувати об’єкт user з полем
orders, в якому будуть лежатиidвсіх замовлень.