Создание Django API используя Django Rest Framework часть 2
Это вторая часть из серии статей про Django API. Первая часть находится здесь. В этой статье мы заново создадим Blog API, но в этот раз используем класс GenericAPIView вместо APIView.
Возможно у вас сразу возникнет вопрос чем GenericAPIView отличается от APIView. Класс GenericAPIView расширяет возможности APIView, добавляя в него часто используемые методы list и detail. Рассмотрим пример использования GenericAPIView.
class ArticleView(ListModelMixin, GenericAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer
Из примера видно что мы унаследовали наш базовый класс от двух классов ListModelMixin
и GenericAPIView
Класс GenericAPIView
обеспечивает базовую функциональность. ListModelMixin
реализует action .list()
(класс ListModelMixin является Mixins , то есть в нем реализуется actions, которые используются для обеспечения базового поведения представления). Для того что использовать .list()
нам нужно связать метод get
с action list
. Для этого внесем соотвествующие изменения в наш файл article/views.py
from rest_framework.generics import GenericAPIView from rest_framework.mixins import ListModelMixin from .models import Article from .serializers import ArticleSerializer class ArticleView(ListModelMixin, GenericAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs)
Этот пример демонстрирует возможность получения списка всех статей, которые есть в настоящее время в нашей базе данных. Для того что бы это заработало, мы определили два обязательных атрибута.
- queryset
Это базовый queryset (запрос к базе) который используется для получение объектов. В нашем случае получение все статей. Если нам бы потребовалось например запрос с фильтрацией и т.п., мы могли бы переопределить методget_queryset
и в через него вернуть требуемый запрос. - serializer_class
Это класс сериализатора, который используется для проверки и десериализации объектов из базы. Мы использовалиArticleSerializer
которые мы создали ранее.
Однако наш сериализатор копирует много информации, которая и также содержится в модели Article. Я думаю, было бы неплохо, если бы мы могли сделать наш код немного более коротким.
ModelSerializer
Давайте обновим наш сериализатор, как показано ниже:
class ArticleSerializer(serializers.ModelSerializer): class Meta: model = Article fields = ('id', 'title', 'description', 'body', 'author_id')
Используя ModelSerializer, мы сразу получаем методыcreate
и update
. Кроме того, так же набор валидаторов по умолчанию.
Поскольку мы создаем приложение CRUD, у нас должна быть возможность создавать статьи. Для этого мы используем другой мixin под названием CreateModelMixin.
Обновим наш article/views.py следующим образом:
from rest_framework.generics import get_object_or_404 from rest_framework.generics import GenericAPIView, CreateModelMixin from rest_framework.mixins import ListModelMixin from .models import Article, Author from .serializers import ArticleSerializer class ArticleView(ListModelMixin, CreateModelMixin, GenericAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer def get(self, request, *args, **kwargs): return self.list(request, *args, **kwargs) def perform_create(self, serializer): author = get_object_or_404(Author, id=self.request.data.get('author_id')) return serializer.save(author=author) def post(self, request, *args, **kwargs): return self.create(request, *args, **kwargs)
Поскольку в каждой статье должен быть автор, то в каждом post запросе есть параметр author_id и он использует его для получения соответствующего автора из базы.
Теперь можно создать статью, отправив POST запрос по адресу http://127.0.0.1:8000/api/articles/, точно так же как в прошлой раз.
Уверен, что вы уже оценили размер кода, которыми мы сократили в сравнение с предыдущей статьей.
Что касается того, что мы определили методы get и post в нашем классе, но не определяли другие методы. Класс GenericAPIViews существуют, как раз для этого. CreateAPIView наследуется от CreateModelMixin, который мы использовали выше, и в нем определили метод post. Поэтому мы можем наследоваться от CreateAPIView и забыть о написании собственного метода post. То же самое относится и к методу get. Мы можем просто наследоваться от ListAPIView и забыть о написании метода get.
Обновим нам класс ArticleView следующим образом:
from rest_framework.generics import get_object_or_404 from rest_framework.generics import CreateAPIView, ListAPIView from .models import Article, Author from .serializers import ArticleSerializer class ArticleView(CreateAPIView, ListAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer def perform_create(self, serializer): author = get_object_or_404(Author, id=self.request.data.get('author_id')) return serializer.save(author=author)
На этом этапе работа API не должна измениться, мы просто упростили наш код.
Но мы можем еще больше сократить наш код. Для этого, используем специальный класс GenericView и скомбинируем создание статьи, и ее публикацию. Этот класс называется ListCreateAPIView. Обновим наш код следующим образом.
from rest_framework.generics import get_object_or_404 from rest_framework.generics import ListCreateAPIView from .models import Article, Author from .serializers import ArticleSerializer class ArticleView(ListCreateAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer def perform_create(self, serializer): author = get_object_or_404(Author, id=self.request.data.get('author_id')) return serializer.save(author=author)
Далее разрешим пользователям обновлять свои статьи. Для этого, нам нужно предоставить пользователю способ получение статьи. DRF предоставляет нам класс RetrieveAPIView. Cоздадим новый класс, который наследуется от RetrieveAPIView, как показано ниже.
from rest_framework.generics import get_object_or_404 from rest_framework.generics import ListCreateAPIView, RetrieveAPIView from .models import Article, Author from .serializers import ArticleSerializer class ArticleView(ListCreateAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer def perform_create(self, serializer): author = get_object_or_404(Author, id=self.request.data.get('author_id')) return serializer.save(author=author) class SingleArticleView(RetrieveAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer
Заметьте, как мы просто добавили еще один класс, и это все? Это основное преимущество от использования GenericViews.
Далее нам нужно обновить наши URL, чтобы воспользоваться новым классом. Изменим article/urls.py
from django.urls import path from .views import ArticleView, SingleArticleView app_name = "articles" # app_name will help us do a reverse look-up latter. urlpatterns = [ path('articles/', ArticleView.as_view()), path('articles/<int:pk>', SingleArticleView.as_view()), ]
С помощью класса RetrieveAPIView мы можем просматривать только одну статью, используя идентификатор статьи. Чтобы иметь возможность обновить статью, нам нужно будет использовать другой GenericView RetrieveUpdateAPIView.
Обновите наш класс SingleArticleView следующим образом:
from rest_framework.generics import RetrieveUpdateAPIView class SingleArticleView(RetrieveUpdateAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer
А затем зайти по адресу http://127.0.0.1:8000/api/articles/1, обратите внимание на появление методов PUT, PATCH
Теперь для таблицы Article у нас есть методы create, list, retrieve и update.
Мы забыли об еще одном методе, я уверен, вы правильно догадались. Метод удаления статей. Для этого мы будем следовать той же тенденции и выберем другой общий класс DRF, который был специально создан для этого. RetrieveUpdateDestroyAPIView. Внесем соответсвующие изменения в нас класс SingleArticleView:
from rest_framework.generics import RetrieveUpdateDestroyAPIView class SingleArticleView(RetrieveUpdateDestroyAPIView): queryset = Article.objects.all() serializer_class = ArticleSerializer
С этим классом, мы можем получить статью, обновить и удалить.
Используя GenericAPIViews мы значительно сократили объем кода, который нам пришлось писать для выполнения операций CRUD. Я не уверен, что можно было бы написать еще меньше кода используя viewsets
. Узнаем об этом следующей статье о использование viewsets.
В классе ArticleView в данном случае можно присваивать значение queryset равное Article.objects без вызова all()
Спасибо Вам за перевод, позволю себе указать на возможные нетерминологические ошибки.
Возможно нужно изменить «используется его» на «использует его»:
«Поскольку в каждой статье должен быть автор, то в каждом post запросе есть параметр author_id и он используется его для получения соответствующего автора из базы»
Не лишние ли тут одно из «только»:
«С помощью класса RetrieveAPIView мы можем только просматривать только одну статью, «
Спасибо за комментарий.
супер, а как насчет организации урлов когда есть n-приложений?
например
http://127.0.0.1:8000/api/books
http://127.0.0.1:8000/api/users
Можно передать имя приложения в качестве переменной во вьюху например так path(«api/str: app_name>», AppView.as_view()), а там уже описать нужную логику в соответствие с именем приложения def blog(request, app_name):…
Что то вроде этого описано тут https://www.webforefront.com/django/accessurlparamstemplates.html
в функции perform_create для сохранения данных через POST, а именно в методе save для сериалайзера (return serializer.save()) не стоит что-либо указывать в параметрах (типа author = author). Поскольку запрос отправки всегда будет вываливаться в ошибку. Корнем ошибки является «TypeError: Article() got an unexpected keyword argument ‘author’\n», но по факту это выливается в «миллион букав», с которым в гугле ничего не сделаешь. Если оставить пустым, то данные без проблем отправляются, валидируются и сохраняются.
За статью огромный лайк!
В коде очевидная ошибка — CreateModelMixin должен импортироваться из rest_framework.mixins, а не rest_framework.generics.
какой в этом толк, если у нас не настроена авторизация? При любом post запросе у нас будет возвращаться вышеупомянутая 404.
Добавление заработало когда сделал следующие исправления:
В сериалайзере ArticleSerializer «author_id» заменил на «author»
В методе perform_create также «author_id» заменил на «author»
У меня тоже
«Поскольку в каждой статье должен быть автор, то в каждом post запросе есть параметр author_id и он используется его для получения соответствующего автора из базы»
Все работает, только post запрос отличается от части 1. Отправляем только вложенный словарь, без названия таблицы. И будет счастье
{
«title»: «Новая статья2»,
«description»: «Описание статьи2»,
«body»: «Текст статьи2»,
«author_id»: 1
}