Создание Django API используя Django Rest Framework часть 2

Spread the love

Это вторая часть из серии статей про 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)

Этот пример демонстрирует возможность получения списка всех статей, которые есть в настоящее время в нашей базе данных. Для того что бы это заработало, мы определили два обязательных атрибута.

  1. queryset
    Это базовый queryset (запрос к базе) который используется для получение объектов. В нашем случае получение все статей. Если нам бы потребовалось например запрос с фильтрацией и т.п., мы могли бы переопределить метод get_queryset и в через него вернуть требуемый запрос.
  2. 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.

Была ли вам полезна эта статья?
[34 / 4.7]

Spread the love
Подписаться
Уведомление о
guest
11 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Александр
Александр
4 лет назад

В классе ArticleView в данном случае можно присваивать значение queryset равное Article.objects без вызова all()

Андрей
Андрей
4 лет назад

Спасибо Вам за перевод, позволю себе указать на возможные нетерминологические ошибки.

Возможно нужно изменить “используется его” на “использует его”:
“Поскольку в каждой статье должен быть автор, то в каждом post запросе есть параметр author_id и он используется его для получения соответствующего автора из базы”

Не лишние ли тут одно из “только”:
“С помощью класса RetrieveAPIView мы можем только просматривать только одну статью, “

Света
Света
4 лет назад

супер, а как насчет организации урлов когда есть n-приложений?
например
http://127.0.0.1:8000/api/books
http://127.0.0.1:8000/api/users

edteam
Администратор
4 лет назад
Reply to  Света

Можно передать имя приложения в качестве переменной во вьюху например так path(“api/str: app_name>”, AppView.as_view()), а там уже описать нужную логику в соответствие с именем приложения def blog(request, app_name):…
Что то вроде этого описано тут https://www.webforefront.com/django/accessurlparamstemplates.html

Yan
Yan
4 лет назад

в функции perform_create для сохранения данных через POST, а именно в методе save для сериалайзера (return serializer.save()) не стоит что-либо указывать в параметрах (типа author = author). Поскольку запрос отправки всегда будет вываливаться в ошибку. Корнем ошибки является “TypeError: Article() got an unexpected keyword argument ‘author’\n”, но по факту это выливается в “миллион букав”, с которым в гугле ничего не сделаешь. Если оставить пустым, то данные без проблем отправляются, валидируются и сохраняются.
За статью огромный лайк!

Юрий
Юрий
3 лет назад

В коде очевидная ошибка – CreateModelMixin должен импортироваться из rest_framework.mixins, а не rest_framework.generics.

Evan
Evan
3 лет назад
author = get_object_or_404(Author, id=self.request.data.get('author_id'))

какой в этом толк, если у нас не настроена авторизация? При любом post запросе у нас будет возвращаться вышеупомянутая 404.

Павел
Павел
3 лет назад

Добавление заработало когда сделал следующие исправления:
В сериалайзере ArticleSerializer “author_id” заменил на “author”
В методе perform_create также “author_id” заменил на “author”

Анонимно
Анонимно
1 год назад
Reply to  Павел

У меня тоже

Сергей
Сергей
2 лет назад

«Поскольку в каждой статье должен быть автор, то в каждом post запросе есть параметр author_id и он используется его для получения соответствующего автора из базы»

Все работает, только post запрос отличается от части 1. Отправляем только вложенный словарь, без названия таблицы. И будет счастье
{   
        “title”“Новая статья2”,
        “description”“Описание статьи2”,
        “body”“Текст статьи2”,
        “author_id”1    
}