Python

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

Spread the love

API (Application Programming Interface — интерфейс прикладного программирования) — это программное обеспечение, которое позволяет двум приложениям общаться друг с другом. В серии статей будет показано как создать API, которое позволит создавать, читать, редактировать и удалять статьи, в нашем тестовом блоге. Это будет проект имитирующий блог у которого должно быть серверное API. Мы рассмотрим различные способы создания API на базе библиотеки Django Rest Framework (DFR). Серия будет состоять из 3 частей. В первой статье мы рассмотрим использование простого класса APIView (часть 1), во второй использования более расширененного класса GenericAPIView (часть 2) и, наконец в третьей использования пожалуй самого продвинутого класса ViewSets (часть 3). Так как мы всего лишь сравниваем применение этих трех классов то обзоры будут короткие и мы не будет подробно рассматривать все возможности выше упомянутых классов.

Итак, приступим. Начнем с установки Django и Django Rest Framework. В терминале создайте каталог и дайте ему любое описательное имя; Затем перейдите в созданный каталог и установите Django, набрав:

pip install django

Так как мы будем использовать Django Rest Framework (DRF) для создания API, установите и его, так же, как мы делали это выше.

pip install djangorestframework

Теперь все готово что бы создать наш первый DRF API.

Для этого нам нужно создать проект Django для работы. Для этого мы запустим следующую команду:

django-admin startproject test_django

Затем запустить миграцию базы данных. Таким образом будет создана база данных по умолчанию (на sqlite), со всеми требуемыми таблицами. Запустите команду:

python manage.py migrate

Теперь можно запустить сервер, чтобы убедиться, что все в порядке:

python manage.py runserver

Сейчас у нас есть проект Django, в который мы можем интегрировать Django rest framework. Откройте файл settings.py и добавьте в атрибут установленных приложениях INSTALLED_APPS = […] строку rest_framework в кавычках.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework'
]

Далее приступим к созданию приложения для нашего проекта. Мы создадим приложение article, в котором мы разместим все, что связано со статьями.

python manage.py startapp article

Потом необходимо включить созданное приложение в список установленных приложений:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'article'
]

Затем перейдите к файлу models.py. В нем, мы создадим наши модели.

Начнем с создания модели авторов:

from django.db import models

class Author(models.Model):
  name = models.CharField(max_length=255)
  email = models.EmailField()

Затем ниже создадим модель статей:

class Article(models.Model):
    title = models.CharField(max_length=120)
    description = models.TextField()
    body = models.TextField()
    author = models.ForeignKey('Author', related_name='articles', on_delete=models.CASCADE)

Далее нужно создать таблицы в базе данных на основе описанные моделей. Для этого нужно провести миграцию. С начало нужно создать файлы миграции а потом ее запустить. Для этого в командной строки запустите две команды:

python manage.py makemigrations
python manage.py migrate

Далее, создадим пользователя с правами администратора:

python manage.py createsuperuser

команда запросить данные нового пользователя. Нужно будет ввести имя пользователя, его email и пароль с подтверждением.

Теперь можно снова запустить сервер и зайти на страницу админки http://127.0.0.1:8000/admin/login/
Далее нужно ввести учетные данные администратора и мы попадем в приложение Django administration.

Далее, нужно зарегистрировать наши модели, чтобы они появлялись на этой странице.
Для этого мы откройте файл article/admin.py  и внесите следующие изменения:

from django.contrib import admin

from .models import Article, Author


admin.site.register(Article)
admin.site.register(Author)

Теперь наша админка должна выглядеть как то так:

Пришло время создать наше API.
Мы начнем с метода через которого можно просмотреть все статьи. Для этого откройте файл article/views.py и вставьте следующий код

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

from .models import Article


class ArticleView(APIView):
    def get(self, request):
        articles = Article.objects.all()
        return Response({"articles": articles})

Далее нам нужно создать URL-адрес, с которого пользователь сможет получить доступ к этому методу. Для этого создайте файл article/urls.py . В этом файле вставьте следующий код.

from django.urls import path

from .views import ArticleView


app_name = "articles"

# app_name will help us do a reverse look-up latter.
urlpatterns = [
    path('articles/', ArticleView.as_view()),
]

Далее нам нужно включить эти URL-адреса в основной файл URL-адресов test_django/urls.py

Чтобы включить наши URL из приложения articles в основную конфигурацию URL, мы будем использовать метод  include  Django. Откройте файл test_django/urls.py, отредактируйте следующим образом:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include('article.urls')),
]

Теперь каждый раз, когда мы будем заходить по адресу http://127.0.0.1:8000/api/articles/ , мы получим доступ ко всем URL-адресам, которые мы определяем внутри articles/urls.py .

Теперь у нас метод API через который мы можем просматривать все статьи в базе данных.

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

Теперь давайте создадим новые объекты в таблице Author и  Article в приложение администратора.


После добавления одного и более автора и статьи, вы может заметить что их отображение не совсем так как хотелось бы

Article object(1) не особо информативное отображение имени объекта. Давай те изменим это, добавим отображение имени статьи. Для этого вернемся в наш article/models.py и добавим метод __str__ Этот метод даст нам удобочитаемое отображение объектов  Article.

class Article(models.Model):
    title = models.CharField(max_length=120)
    description = models.TextField()
    body = models.TextField()
    author = models.ForeignKey('Author', related_name='articles', on_delete=models.CASCADE)

    def __str__(self):
        return self.title

После добавления этого метода в класс Article, наша админка будет выглядеть как то так:

Добавьте такой же метод в класс Author.

class Author(models.Model):
    name = models.CharField(max_length=255)
    email = models.EmailField()

    def __str__(self):
        return self.title

Теперь у нас должно быть несколько статей  в таблице Articles , проверим работает ли доступ API к статьям. Перейдите по адресу http://127.0.0.1:8000/api/articles

Опс!!!

TypeError. Серьезно? После всего того что мы сделали?
Ошибка возникла в строке return Response({"articles": articles}) где мы пытаемся сериализовать (то есть сконвертировать из объектов в формат JSON) список объектов articles . Но так как мы еще не указали класс для сериализации объектов статей, мы получили ошибку.

Чтобы это исправить, познакомимся с понятием  Serializers.

Serializers (Сериализаторы) позволяют преобразовывать сложные данные, такие как наборы запросов querysets и объекты моделей, в типы данных Python, которые затем можно легко преобразовать в JSON, XML или другие content types.

Теперь, давайте создадим сериалайзер, который преобразует наши статьи в список Python, который мы можем вернуть в API запросе.
Создайте новый файл в папке статей и назовите его что-то вроде  article/serializers.py. В этот файл добавьте следующий код:

from rest_framework import serializers

class ArticleSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=120)
    description = serializers.CharField()
    body = serializers.CharField()

Как вы можете видеть, мы пока еще не сериализуем автора (author). Мы это сделаем позже.
Следующий шаг — нужно добавить этот сериализатор в наши представления (views) и сделать так чтобы представление сериализовало статьи. Следующий код показывает, как это делается.

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

from .models import Article
from .serializers import ArticleSerializer

class ArticleView(APIView):
    def get(self, request):
        articles = Article.objects.all()
        # the many param informs the serializer that it will be serializing more than a single article.
        serializer = ArticleSerializer(articles, many=True)
        return Response({"articles": serializer.data})

После этого снова запустите сервер и перейдите к http://127.0.0.1:8000/api/articles/ . Должно получится что то типа такого:

Далее добавим метод API для создание статьи. Можно было бы создать еще один класс с методом post и зарегистрировать его в URL-адресах так же, как мы делали для метода get. Однако вместо создания нового класса, воспользуемся тем что  APIView  позволяет нам указать несколько HTTP-методов для одного класса. Поэтому добавим метод post  внутри нашего  ArticleView .

class ArticleView(APIView):
    def get(self, request):
        articles = Article.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response({"articles": serializer.data})

    def post(self, request):
        article = request.data.get('articles')

        # Create an article from the above data
        serializer = ArticleSerializer(data=article)
        if serializer.is_valid(raise_exception=True):
            article_saved = serializer.save()
        return Response({"success": "Article '{}' created successfully".format(article_saved.title)})

Как видите, мы используем созданный ранее сериализатор, чтобы создать новый объект статьи из данных которые мы получаем от пользователя. Как мы уже говорили ранее, мы проигнорировали поле автора в нашем сериализаторе, и поэтому оно не возвращается в полученном ответе. Чтобы мы могли использовать наш сериализатор для создания статей, нам нужно добавить поле  author_id  в сериализатор, а затем нам потребуется реализовать метод create в сериализаторе, который сообщит сериализатору, что делать, когда вызывается метод  save сериализатора.
Обновим наш ArticleSerializer, чтобы он выглядел следующим образом.

from rest_framework import serializers

from .models import Article


class ArticleSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=120)
    description = serializers.CharField()
    body = serializers.CharField()
    author_id = serializers.IntegerField()

    def create(self, validated_data):
        return Article.objects.create(**validated_data)

Учитывая, что мы создали автора с панели администратора, теперь вы можете использовать postman или любой другой REST клиент для создания статьи (например какой нибудь REST плагин в хроме). Пример такого запроса показан ниже.

Благодаря этому каждый теперь сможет создавать статьи используя API. Теперь создадим возможность редактирования статьи.

Для этого мы обновим API  articles , чтобы пользователи могли обновить статью, отправив запрос PUT. Сначала добавим новый path  в файл article/urls.py.

from django.urls import path

from .views import ArticleView


app_name = "articles"

# app_name will help us do a reverse look-up latter.
urlpatterns = [
    path('articles/', ArticleView.as_view()),
    path('articles/<int:pk>', ArticleView.as_view())
]

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

from rest_framework import serializers

from .models import Article


class ArticleSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=120)
    description = serializers.CharField()
    body = serializers.CharField()
    author_id = serializers.IntegerField()

    def create(self, validated_data):
        return Article.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.title = validated_data.get('title', instance.title)
        instance.description = validated_data.get('description', instance.description)
        instance.body = validated_data.get('body', instance.body)
        instance.author_id = validated_data.get('author_id', instance.author_id)

        instance.save()
        return instance

Что происходит в методе  update ? В том случае если мы что то передаем в экземпляр статьи, который мы хотим обновить, мы переназначаем это значение, в противном случае мы сохраняем старое значение атрибута.

Теперь создадим обработку запроса на обновление статьи. Мы должны определить метод  put  в нашем  ArticleView , этот метод должен принять параметр  pk из URL, найти требуемый экземпляр из базы и запустить сериалайзер на обновление. Внесем соотвествующие изменения в файл article.views.py.

from rest_framework.generics import get_object_or_404
from rest_framework.response import Response
from rest_framework.views import APIView

from .models import Article
from .serializers import ArticleSerializer


class ArticleView(APIView):
    def get(self, request):
        articles = Article.objects.all()
        serializer = ArticleSerializer(articles, many=True)
        return Response({"articles": serializer.data})

    def post(self, request):
        article = request.data.get("articles")
        # Create an article from the above data
        serializer = ArticleSerializer(data=article)
        if serializer.is_valid(raise_exception=True):
            article_saved = serializer.save()
        return Response({"success": "Article '{}' created successfully".format(article_saved.title)})

    def put(self, request, pk):
        saved_article = get_object_or_404(Article.objects.all(), pk=pk)
        data = request.data.get('articles')
        serializer = ArticleSerializer(instance=saved_article, data=data, partial=True)

        if serializer.is_valid(raise_exception=True):
            article_saved = serializer.save()

        return Response({
            "success": "Article '{}' updated successfully".format(article_saved.title)
        })

Мы передаем partial=True в сериализатор, поскольку хотим иметь возможность обновлять только некоторые поля, но не обязательно все сразу. 
Используя Postman или любой другой инструмент, теперь можно обновить статью, отправив запрос по адресу http://127.0.0.1:8000/articles/<article id> с данными, которые вы хотите обновить.

Последнее что нам осталось добавить для создания полноценного CRUD API, это метод удаления. Для этого мы создадим метод  delete  в APIView , который будет принимать  id  статьи, в качестве аргумента.

def delete(self, request, pk):
    # Get object with this pk
    article = get_object_or_404(Article.objects.all(), pk=pk)
    article.delete()
    return Response({
        "message": "Article with id `{}` has been deleted.".format(pk)
    }, status=204)

В методе delete все, что мы делаем — это получаем статью из базы, и если она существует, удаляем ее и затем возвращаем ответ пользователю.

Теперь у нас есть полностью функционирующие API, с помощью которого мы можем выполнить все основные задачи API, т. е. Операции Create Read Update Delete (CRUD).

Во второй части этой серии мы рассмотрим, как использовать Django Rest Framework GenericAPIView.

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

Spread the love
Editorial Team

View Comments

  • Гигантское спасибо за простой и понятный разбор!

  • 1. if serializer.is_valid(raise_exception=True):
    обьясните пожалуйства почему raise_exception = True
    2. Где мы указали на удаление с помощью чего она доходит до функции delete
    3. какие параметры принимает request = post get delete put?

    • 1. raise_exception=True это параметр функции is_valid. Метод .is_valid () принимает необязательный флаг raise_exception, который заставляет его вызывать исключение serializers.ValidationError, если имеются ошибки проверки. Более подробно тут: https://www.django-rest-framework.org/api-guide/serializers/#raising-an-exception-on-invalid-data
      2 В последнем абзаце описан метод delete он должен быть частью класса ArticleView так же как метод put/post/get. При обращение с фронта методом HTTP delete класс ArticleView вызовет свой метод delete
      3 Третий вопрос для меня не понятен.

      • Во втором вопросе, скорее всего, имелось ввиду какой запрос нужно отправить для удаления статьи. И ответом будет: пустой запрос с методом delete по url http://127.0.0.1:8000/articles/ В таком случае отработает метод delete класса ArticleView и статья будет удалена.

  • Огромнейшее вам спасибо! Это лучшая инструкция по новой версии Джанго 2.2.6. Только для мака python3 команды начинать нужно и pip3 вместо pip. Я дошла до момента POST с помощью программы postman. Как этой программой пользоваться еще не знаю. Но сервер мне отдает
    [04/Oct/2019 12:57:51] "POST /api/articles/ HTTP/1.1" 400 41

    И было бы классно, если бы вы написали как выполнять формирование пакета данных в формате JSON, его отправку по REST API на внешний сервер и сохранение пакета в текстовый файл.

      • ошибка в строке
        serializer = ArticleSerializer(instance=saved_article, data=data, partial=True)
        что бы put запрос работал
        serializer = ArticleSerializer(instance=saved_article, data=request.data, partial=True)

    • По поводу python/python3 и pip/pip3 - погуглите про создание python проектов в виртуальном окружении. Грубо говоря, вы создаете изолированную среду разработки, где у вас загружены пакеты конкретно под данный проект. Работая в такой среде вам необязательно указывать версию питона или pip, т.к. она будет определена автоматически. А вот когда вы работаете, что называется, глобально, требуется указывать версию вручную, т.к. на маке и линуксе из коробки идет 2 версия, и 3 версию надо ставить дополнительно. Вообще использование виртуального окружения это полезное и можно сказать обязательное условие, очень советую использовать

    • def post(self, request):
      # исправить опечатку в аргументе get
      article = request.data.get("articles")

      • def put(self, request, pk):
            ....
            serializer = ArticleSerializer(saved_article, data, partial=True)
        

        в ArticleSerializer убрать названия параметров, иначе ошибка

  • Спасибо за отличное руководство -- намного лучше и понятнее чем официальный туториал на сайте Django REST Framework.

    Один вопрос:
    После удаления статьи через запрос DELETE мы отправляем сообщение "Article with id `{}` has been deleted." и статус 204. Но если отправлять такой статус, то приходит пустой ответ, то есть запрос отрабатывает и возвращается 204 статус, но сообщение "Article with id `{}` has been deleted." не приходит в теле ответа. Если же вернуть другой статус, например 200, то сообщение приходит. Пробовал через Postman и через CURL
    Почему так происходит?

    • "The HTTP 204 No Content" код ответа статуса успеха указывает, что запрос имеет успех, и что клиенту не нужно уходить со своей текущей страницы. Но при этом в ответе нет контента.
      HTTP 204 No Content: The server successfully processed the request, but is not returning any content
      https://stackoverflow.com/questions/2342579/http-status-code-for-update-and-delete
      Если нужно передать сообщение клиенту то лучше поменять статус на 200

  • Не сильно пока понимаю в Django
    Подскажите пожалуйста, как это restfull приложение прикрутить уже к существующим в проекте моделям с таблицами? что и где прописать?

  • В строковом методе автора self.name наверное имелось ввиду.

  • Продублирую чувака свыше.
    Вот мы создали с вами API со статьей, а как нам теперь задействовать её потенциал? 1) Как выгрузить данные на template со своим оформлением вместо дефолтного RESTful API лэндинга? 2) Как другим пользователям предоставить наш API, т.е вот url sitename.com/articles, зайдя сюда я хочу увидеть статьи с картинками и описанием, а если обратиться запросом то получить json-ответ? Надеюсь, меня кто-нибудь да поймет, потому что тема очееееень интересная

  • Обнаружили ошибку:
    <>
    - Вместо return self.title надо return self.name

Recent Posts

Vue 3.4 Новая механика v-model компонента

Краткий перевод: https://vuejs.org/guide/components/v-model.html Основное использование​ v-model используется для реализации двусторонней привязки в компоненте. Начиная с Vue…

11 месяцев ago

Анонс Vue 3.4

Сегодня мы рады объявить о выпуске Vue 3.4 «🏀 Slam Dunk»! Этот выпуск включает в…

11 месяцев ago

Как принудительно пере-отобразить (re-render) компонент Vue

Vue.js — это универсальный и адаптируемый фреймворк. Благодаря своей отличительной архитектуре и системе реактивности Vue…

2 года ago

Проблемы с установкой сертификата на nginix

Недавно, у меня истек сертификат и пришлось заказывать новый и затем устанавливать на хостинг с…

2 года ago

Введение в JavaScript Temporal API

Каким бы ни было ваше мнение о JavaScript, но всем известно, что работа с датами…

2 года ago

Когда и как выбирать между медиа запросами и контейнерными запросами

Все, кто следит за последними событиями в мире адаптивного дизайна, согласятся, что введение контейнерных запросов…

2 года ago