Создание 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
Подписаться
Уведомление о
guest
32 Комментарий
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Антон
Антон
5 лет назад

Замечательно, все кратко и по делу!

Мария
Мария
5 лет назад

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

sacls
sacls
5 лет назад

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

Alexey
Alexey
5 лет назад
Reply to  Editorial Team

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

Kate
Kate
5 лет назад

Огромнейшее вам спасибо! Это лучшая инструкция по новой версии Джанго 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 на внешний сервер и сохранение пакета в текстовый файл.

rbhbkk
rbhbkk
4 лет назад
Reply to  Editorial Team

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

ula
ula
3 лет назад
Reply to  rbhbkk

Спасибо братан ни хрена не получалось

Анонимно
Анонимно
2 лет назад
Reply to  rbhbkk

спс большое!!!

exegun
exegun
4 лет назад
Reply to  Kate

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

Мари
Мари
2 лет назад
Reply to  Kate

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

Мари
Мари
2 лет назад
Reply to  Мари
def put(self, request, pk):
    ....
    serializer = ArticleSerializer(saved_article, data, partial=True)

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

Last edited 2 лет назад by Мари
Александр
Александр
4 лет назад

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

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

edteam
Администратор
4 лет назад

«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

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

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

muhtor
muhtor
4 лет назад

Wonderful

Серж
Серж
4 лет назад

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

Valera
Valera
4 лет назад

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

exegun
exegun
4 лет назад
Reply to  Valera

https://www.django-rest-framework.org/topics/html-and-forms/ — есть вот такая ссыль.
Сам еще не вникал

Виталий
Виталий
4 лет назад

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

Денис
Денис
4 лет назад

У меня вопрос когда я ввожу python manage.py makemigrations python manage.py migrate
python manage.py createsuperuser у меня выходит ModuleNotFoundError: No module named ‘rest_frameworkarticle’ Что делать????!

Vasya
Vasya
4 лет назад
Reply to  Денис

Подозреваю что у тебя в файле settings.py в INSTALLED APPS ошибка.

Антон
Антон
3 лет назад
Reply to  Денис

в списке INSTALLED APPS должно быть
‘rest_framework’,
‘article’
а вы слитно написали

Harv
Harv
4 лет назад

Здравствуйте!
При POST методе выходит ошибки 400 Bad Request
{
«non_field_errors»: [
«No data provided»
]
}

Хотя параметры всё записываю в запросе.
{
«tasks»: {
«task_title»: «Test2»,
«task_description»: «temp test2»,
«deadline»: «2020-05-10T00:00:00+06:00»,
«performed»: «»,
«ID»: 2
}
}

Класс для serializers использую следующий:
class TaskSerializers(serializers.Serializer):
task_title = serializers.CharField(max_length=100)
task_description = serializers.CharField()
deadline = serializers.DateTimeField()
performed = serializers.BooleanField(default=False)
ID = serializers.IntegerField()

Подскажите в чем ошибка

Анонимно
Анонимно
4 лет назад
Reply to  Harv

Разобрался…. Ошибка в запросе

Den
Den
4 лет назад

Спасибо !
Хороший туториал !

Есть вопрос — вот мы получая список статтей или одну статью получаем id автора статьи, как получить например имя автора в нагрузку ?

Анонимно
Анонимно
3 лет назад
Reply to  Den

JOIN TABLE

Дмитрий
Дмитрий
4 лет назад

Спасибо Вам за статью!

Дмитрий
Дмитрий
4 лет назад

Классная статья! Эх, теперь бы то же самое, но для raw-запросов.

Геннадий
Геннадий
3 лет назад

Статья отличная. Много объяснила на практике. Все работает.
Но не хвалило обработки ключа записи. Т.е. выдаваемый объект не содержит id. А это в API редко когда не используется. Все таки привязка к записи нужна на ФронтЭнде.
А при добавлении в сериализатор id = serializers.IntegerField() — не срабатывает POST метод (id -обязательное поле). Хотя оно AUTOINCREMENT в SQl.
Объявление поля как необязательное пришлось искать отдельно для доработки.
А другим это было быть полезно получить сразу из статьи.

Last edited 3 лет назад by Геннадий
Анонимно
Анонимно
2 лет назад

спасибо