Создание Django API используя Django Rest Framework часть 1
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.
Замечательно, все кратко и по делу!
Гигантское спасибо за простой и понятный разбор!
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 на внешний сервер и сохранение пакета в текстовый файл.
Ответ 400 Bad Request означает что сервер обнаружил в запросе клиента синтаксическую ошибку.
По postman на хабре есть статья https://habr.com/ru/company/kolesa/blog/351250/ или тут https://www.software-testing.ru/library/testing/testing-tools/2638-postman
там же описано как отправить данные в JSON
ошибка в строке
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«)
в 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 приложение прикрутить уже к существующим в проекте моделям с таблицами? что и где прописать?
Wonderful
В строковом методе автора self.name наверное имелось ввиду.
Продублирую чувака свыше.
Вот мы создали с вами API со статьей, а как нам теперь задействовать её потенциал? 1) Как выгрузить данные на template со своим оформлением вместо дефолтного RESTful API лэндинга? 2) Как другим пользователям предоставить наш API, т.е вот url sitename.com/articles, зайдя сюда я хочу увидеть статьи с картинками и описанием, а если обратиться запросом то получить json-ответ? Надеюсь, меня кто-нибудь да поймет, потому что тема очееееень интересная
https://www.django-rest-framework.org/topics/html-and-forms/ — есть вот такая ссыль.
Сам еще не вникал
Обнаружили ошибку:
<>
— Вместо return self.title надо return self.name
У меня вопрос когда я ввожу python manage.py makemigrations python manage.py migrate
python manage.py createsuperuser у меня выходит ModuleNotFoundError: No module named ‘rest_frameworkarticle’ Что делать????!
Подозреваю что у тебя в файле settings.py в INSTALLED APPS ошибка.
в списке INSTALLED APPS должно быть
‘rest_framework’,
‘article’
а вы слитно написали
Здравствуйте!
При 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()
Подскажите в чем ошибка
Разобрался…. Ошибка в запросе
Спасибо !
Хороший туториал !
Есть вопрос — вот мы получая список статтей или одну статью получаем id автора статьи, как получить например имя автора в нагрузку ?
JOIN TABLE
Спасибо Вам за статью!
Классная статья! Эх, теперь бы то же самое, но для raw-запросов.
Статья отличная. Много объяснила на практике. Все работает.
Но не хвалило обработки ключа записи. Т.е. выдаваемый объект не содержит id. А это в API редко когда не используется. Все таки привязка к записи нужна на ФронтЭнде.
А при добавлении в сериализатор id = serializers.IntegerField() — не срабатывает POST метод (id -обязательное поле). Хотя оно AUTOINCREMENT в SQl.
Объявление поля как необязательное пришлось искать отдельно для доработки.
А другим это было быть полезно получить сразу из статьи.
спасибо