Django 5 для начинающих

Прогресс по курсу:  9/1004

8.4 Представления
3 из 4 шагов пройдено
0 из 5 баллов  получено

Django REST Framework поставляется с набором типовых представлений и миксинов, которые можно использовать для разработки представлений API. Они предоставляют функциональность извлечения, создания, обновления или удаления модельных объектов. Все типовые поставляемые с фреймворком REST миксинные классы и представления находятся по адресу https://www.django-rest-framework.org/api-guide/generic-views/.

APIView

Класс APIView обеспечивает обычно требуемое поведение для стандартных представлений списка и сведений. С классом APIView мы можем переписать корневое представление как представление на основе классов. Они предоставляют методы действий, такие как get(), post(), put(), patch() и delete(), а не определяют методы обработчика.

Создание представлений с помощью APIView

Давайте посмотрим, как создавать представления с помощью APIView.

Добавим в файл views.py приложения blog_api, следующий код:

from django.http import Http404

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

from blog.models import Post
from .serializers import PostSerializer


class PostList(APIView):
    def get(self, request, format=None):
        posts = Post.objects.all()
        serializer = PostSerializer(posts, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = PostSerializer(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)


Помимо методов get() и post() можно прописывать и другие для обработки соответствующих типов запросов:

  • get() – вызывается при GET-запросах;
  • post() – вызывается при POST-запросах;
  • put() – вызывается при PUT-запросах;
  • patch() – вызывается при PATCH-запросах;
  • delete() – вызывается при DELETE-запросах.

Рассмотрим их при создании детального отображения постов, добавим в файл views.py приложения blog_api, следующий код:

class PostDetail(APIView):
    def get_object(self, pk):
        try:
            return Post.objects.get(pk=pk)
        except Post.DoesNotExist:
            raise Http404

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

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

    def patch(self, request, pk, format=None):
        post = self.get_object(pk)
        serializer = PostSerializer(post,
                                    data=request.data,
                                    partial=True)
        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):
        queryset = self.get_object(pk)
        queryset.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


Давайте проверим работу, для этого перейдем по адресу http://127.0.0.1:8000/api/:


Давайте попробуем создать новую запись, для этого опустимся в самый низ страницы и введем тестовую запись:


После нажатия кнопки POST мы видим успешное выполнение данной операции, а именно функции post в классе PostList:

Мы видим успешный ответ сервера HTTP 201 Created, и нашу запись.


Вернемся на главную страницу нашего API http://127.0.0.1:8000/api/ и проверим наш список постов:


Далее давайте перейдем к просмотру нашей записи через API, в моем случае у записи id=6, поэтому перейдем по адресу http://127.0.0.1:8000/api/6/.

На скриншоте выше мы видим что у нас есть кнопки PUT, PATCH и DELETE. Это именно те функции которые мы добавили в нашем представлении PostDetail.

У вас здесь может возникнуть вопрос, а как одно и то же представление для одного и того же URL-адреса «понимает», какой из методов следует вызывать, когда приходит какой-либо запрос от клиента? В действительности, все просто. Когда клиент формирует запрос, то в его заголовке прописывается тип этого запроса: GET, POST, DELETE, PUT и т.п. Затем, алгоритм, заложенный в представления DRF автоматически вызывает метод, связанный с поступившим запросом. Если нужный метод не находится, то клиенту возвращается JSON-строка с сообщением, что метод не разрешен.


  • Комментариев
Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий

Оставлю это здесь:

{
    "id": 6,
    "author": 1,
    "title": "Check adding the post",
    "body": "Test",
    "created": "2023-07-12T06:49:17.655000Z"
}
Изменен Илья Перминов

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

@Виктор_Русинович, Можете сообщить, какие моменты вам были более всего не понятны? Мы обязательно их разберем и добавим в эту лекцию.

@Илья_Перминов

Комментарий ниже представляет мое мнение, возможно, у Вас иной взгляд на подачу материала

Django REST Framework поставляется с набором типовых представлений и миксинов, 

-1)  Вы упомянули Миксины которые наследуются от базового класса АПИ представления, но это материал никак не пригодится на этой странице, а главное не  поможет разобраться с вопросами, которые могут возникнуть при разборе материала.  тут была бы в больше степени к месту эта ссылка на документацию АПИ представления: 

https://www.django-rest-framework.org/api-guide/views/

или на ее русскоязычный аналог(раз курс на руском)

https://ilyachch.gitbook.io/django-rest-framework-russian-documentation/overview/navigaciya-po-api/views

 

2) PATCH() - запросы не разбирались ранее в курсе, было бы здорово сказать что они позволяют изменять запись не переписывая строку в БД целиком

3) Было бы здорово рассказать о модулях ответ и статуса в АПИ более подробно, зачем они нужны, что передают. что будет если их не добавлять

from rest_framework.response import Response

from rest_framework import status

 

4) В предыдущих частях после написания блок кода, был минимальный разбор поведения этого кода, в этом уроке просто даны блоки кода. Вы даете материал людям не знакомым  с ДРФ, его надо объяснять более детально или с таким же успехом можно было просто скопировать чужой проект с гита и начать его разбирать

def get(self, request, pk, format=None)

появились аттрибуты pk - primary key и формат = None - на странице урока нет материала (или ссылки на материал) что бы понять , что PK - это аббревиатура, ключа к записи в БД, а формат - отвечает за передачу расширение фаила в котором передаются сериализованные данные(про формат все равно до конца не понял как он работает и какие аргументы кроме Нан можно передавать и как они будут менять поведение). 

5) почему где то мы возвращаем статус а где то нет?

Пример со статусом:

if serializer.is_valid():

serializer.save()

return Response(serializer.data, status=status.HTTP_201_CREATED)

Пример без статуса:

if serializer.is_valid():

serializer.save()

return Response(serializer.data)

6) Как диспетчер понимает что надо начинать с выполнения функции get object? 

class PostDetail(APIView):

   def get_object(self, pk):

      try:

7) а если partial=False  то поведение будет как у Put()?

serializer = PostSerializer(transformer, data=request.data, partial=True)

В целом причины всех описанных выше вопросов в недостатке теоретической базы, которая дается к уроку и описанию функций, классов их  атрибутов и поведения. Возможно курс не тестировался новичками, а проверялся опытными разработчиками у которых не было вопросов ни к материалу ни к коду. 

@Виктор_Русинович, в целом согласен тут.
Возможно уже забуду к концу 8 модуля, поэтому напишу тут, ну и с этим сообщением перекликается. 
Не хватает в курсе часто разбора атрибутов, которые используются в функциях. Ну и хотелось бы знать все атрибуты, которые можно передавать, какие обязательные, какие нет и т.д. Нужно описание их и пару примеров. С методами классов то же самое.

Мб можно прям указать что это доп. материал, для лучшего понимания.

@Виктор_Русинович, да, по сравнению с прошлыми разделами этого курса, здесь очень не хватает разбора информации.

@Дмитрий_Харламов, обязательно сделаем, курс продолжает дорабатывается, в ближайшее время будут добавлены новые задачи и будет переход на Django 5.

 Почему мы передаем именно такой набор полей в POST? 

А как же slug - оно же обязательное?
Зачем передавать created? Это же автоматически прописываемое поле. Почему не передаем publish? Непонятно, хотелось бы пояснений

Да, можно не передавать неиспользуемое поле id в PUT запросе - для этого его нужно включить в коллекцию read_only_fields в Meta:

read_only_fields = ("id",)
Изменен ilya kutaev

@ilya_kutaev, потому что в сериализаторе у нас следующий код:

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        fields = (
            "id",
            "author",
            "title",
            "body",
            "created",
        )
        model = Post

То есть мы указали, по каким полям мы будем создавать новые записи. Все поля, которые обязательные, и мы их не добавили сюда, будут просто пустые. Я не стал добавлять в этот API все необходимые поля, чтобы скриншоты не были на пол экрана, но соглашусь что Slug тут все таки будет правильнее добавить.

@Илья_Перминов, именно это я и имел в виду, очевидно же, что мы "ломаем" функционал, добавляя посты без слага.
Так не сильно длиннее, но более наглядно:

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ("id", "author", "title", "body", "created", "updated", "published", "slug", "status")
        read_only_fields = ("id", "slug", "created", "updated", "published",)

У меня slug формируется автоматически в модели в save(), если он пустой, "а за ребят обидно" )))

Про стремление к сокращению понял, можно бы это кратко отметить по тексту - "возьмем для примера основные поля, часть опустим для краткости". Иначе возникает ощущение тайного смысла, который ты упустил ))

Изменен ilya kutaev

@ilya_kutaev, slug и status обязательно добавим, я думаю в ближайшее время переделаем все скрины.

А в чем логика называть переменную transformers?Если мы назовем ее posts,ничего же не поменяется?А к восприятие кода как-будто легче будет ,поправьте если я не прав.

@User_678998186, были жалобы на похожие названия переменных, от смены названия переменных ничего не изменится, мне то-же так больше нравится - posts, изменил в лекции.

здесь нигде не сказано что это нужно себе добавлять, поправьте пожалуйста !

 

Создание представлений с помощью APIView

Давайте посмотрим, как создавать представления с помощью APIView. Модуль файла views.py выглядит следующим образом:

from django.http import Http404

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

from blog.models import Post
from .serializers import PostSerializer


class PostList(APIView):
    def get(self, request, format=None):
        transformers = Post.objects.all()
        serializer = PostSerializer(transformers, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = PostSerializer(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)


Помимо методов get() и post() можно прописывать и другие для обработки соответствующих типов запросов:

  • get() – вызывается при GET-запросах;
  • post() – вызывается при POST-запросах;
  • put() – вызывается при PUT-запросах;
  • patch() – вызывается при PATCH-запросах;
  • delete() – вызывается при DELETE-запросах.

Рассмотрим их при создании детального отображения постов:

class PostDetail(APIView):
    def get_object(self, pk):
        try:
            return Post.objects.get(pk=pk)
        except Post.DoesNotExist:
            raise Http404

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

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

    def patch(self, request, pk, format=None):
        transformer = self.get_object(pk)
        serializer = PostSerializer(transformer,
                                    data=request.data,
                                    partial=True)
        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):
        transformer = self.get_object(pk)
        transformer.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

@No_Name, добавил, спасибо за замечание.

У кого ошибка при создании поста 400 не пугайтесь, дело скорее всего в данных!

вот у меня например при таких даннх

{     "id": 6,     "author": 1,     "title": "Check adding the post",     "body": "Test",     "created": "2023-07-12T06:49:17.655000Z" }

"id": 6 уже есть по этому я указал то айди которого нет но это не важно на самом деле так как пост создастся следующим в БД но вот что важно это "author": 1, цифра автора у меня админ под цифрой 2 почему то по этому и была ошибка, или вы можете указать любую другою цифру вашего автора!

На данном этапе возникает ошибка из-за версий API:

ModuleNotFoundError: No module named 'blog_api_v1

Потому что в urls.py мы указывали icnlude, но кроме этого ничего не делали. Когда закомментил эти строки, то сервер запустился

path("api/v1/", include("blog_api_v1.urls")), # API версия 1

path("api/v2/", include("blog_api_v2.urls")), # API версия 2

@Константин_Малыхин, Мы версионирование API не добавляем, это было указано в качестве примера как делают на практике.

А в файл mysite/urls.py добавим новый маршрутизатор:

path("api/", include("blog_api.urls")),

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

Например создание маршрутов типа:

path("api/v1/", include("blog_api_v1.urls")), # API версия 1
path("api/v2/", include("blog_api_v2.urls")), # API версия 2

 Поможет вам поддерживать v1 API в течение определенного периода времени, одновременно запуская новую, обновленную v2, и избегая поломки других приложений, которые полагаются на бэкэнд вашего API.

@Илья_Перминов, Хорошо:) Тогда логично, а то думал, что добавляем!

А почему для класса PostList не определен метод post? И почему у нас определен post для postlist? мы же вроде добавляем отдельный пост..?

Изменен Alice Ageeva

@Alice_Ageeva, Если я правильно вас понял, то в классе PostList мы определяем метод post, чтобы создавать новые посты, когда мы просматриваем все записи блога(get метод). Я думаю находясь в детальном просмотре записи (класс PostDetail) не совсем будет удобно добавлять еще новую запись. В детальном просмотре записи логичнее будет только редактировать или удалять эту запись.

@Илья_Перминов, да, согласна, спасибо)