В современном мире веб-разработки API (Application Programming Interface) обеспечивает взаимодействия между различными приложениями и сервисами.
REST API, стали стандартом для создания веб-сервисов благодаря их простоте и гибкости.
Наша сегодняшняя цель - подробно разобраться в процессе создания REST API с использованием Django и Django REST Framework, разбираясь в каждом шаге и его значение. Мы также рассмотрим, почему создание API важно и как это может быть полезно в ваших будущих проектах.
Почему REST API важны?
REST — это архитектурный стиль, который определяет набор ограничений и принципов для создания веб-сервисов.
REST API позволяют:
Обеспечивать взаимодействие между различными приложениями. Например, мобильное приложение может взаимодействовать с сервером через API.
Создавать микросервисную архитектуру. Разделение приложения на независимые сервисы улучшает масштабируемость и гибкость.
Обеспечивает совместимость с различными клиентами. REST API используют стандартные HTTP-методы и форматы данных, такие как JSON, что облегчает интеграцию в другие сервисы.
План действий по разработке
Теперь давайте определим наш план действий и поскорее приступим к написанию кода!
Настроим проект Django и приложения API.
Создадим модели данных с использованием Django ORM.
Настроим Django REST Framework.
Создадим сериализаторы для преобразования данных.
Реализуем представление и маршруты для обработки запросов.
Сделаем фильтрацию, поиск, сортировку, пагинацию.
Протестируем наше API.
Развернем проект в облаке Amvera тремя командами в IDE через загрузку кода в привязанный Git-репзиторий (что намного проще настройки VPS).
Каждый этап нашей статьи будет сопровождаться объяснениями и примерами кода, полный код вы сможете найти в репозитории на GitHub.
Приступаем!)
Установим Django и Django REST Framework
Сначала необходимо установить Django и DRF. Рекомендуем создать виртуальное окружение для изоляции зависимостей.
Создадим и активируем виртуальное окружение:
# Создаём виртуальное окружение
python -m venv venv
# Активация виртуального окружения
# На Windows:
.\venv\Scripts\activate
# На macOS и Linux:
source venv/bin/activate
Установим необходимые пакеты для работы
pip install django
pip install djangorestframework
pip install django-filter
pip install djangorestframework-simplejwt
Создадим папку проекта и нашего приложения
django-admin startproject Site
cd Site
python manage.py startapp api
Определим модели
В файле api/models.py
создадим следующие модели:
Profile: Расширяет встроенную модель
User
, добавляя биографию пользователя.Category: Категория для постов.
Post: Публикация, связанная с автором (пользователем) и категорией.
Comment: Комментарий к посту от пользователя.
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
"""Расширение модели User для добавления дополнительных полей"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
def __str__(self):
return f'Профиль пользователя {self.user.username}'
class Category(models.Model):
"""Модель категории для постов"""
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
def __str__(self):
return self.name
class Post(models.Model):
"""Модель поста"""
author = models.ForeignKey(User, related_name='posts', on_delete=models.CASCADE)
category = models.ForeignKey(Category, related_name='posts', on_delete=models.SET_NULL, null=True)
title = models.CharField(max_length=200)
content = models.TextField()
published = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
likes = models.ManyToManyField(User, related_name='liked_posts', blank=True)
class Meta:
ordering = ['-published']
def __str__(self):
return self.title
class Comment(models.Model):
"""Модель комментария"""
post = models.ForeignKey(Post, related_name='comments', on_delete=models.CASCADE)
author = models.ForeignKey(User, related_name='comments', on_delete=models.CASCADE)
text = models.TextField()
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['created']
def __str__(self):
return f'Комментарий от {self.author.username} на {self.post.title}'
Почему мы делаем именно так:
Модели определяют структуру данных в приложении.
Используя Django ORM, мы можем работать с базой данных через Python-код без необходимости писать SQL-запросы.
Определение отношений между моделями (OneToOne, ForeignKey, ManyToMany) позволяет связать данные и обеспечить целостность информации.
После определения моделей необходимо создать и применить миграции
python manage.py makemigrations
python manage.py migrate
Перейдем к настройке Django REST Framework
Настройка settings.py
В файле Site/settings.py
добавим необходимые приложения и настройки и с использованием DEFAULT_PAGINATION_CLASS
и PAGE_SIZE
организуем пингацию
INSTALLED_APPS = [
# Сторонние приложения
'rest_framework',
'rest_framework_simplejwt',
'django_filters',
# Наше приложение
'api',
# Встроенные приложения Django
'django.contrib.admin',
'django.contrib.auth',
# ... остальные приложения
]
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication', # JWT аутентификация
'rest_framework.authentication.SessionAuthentication', # Сессионная аутентификация для браузера
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticatedOrReadOnly', # Разрешения по умолчанию
),
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend', # Фильтрация
'rest_framework.filters.SearchFilter', # Поиск
'rest_framework.filters.OrderingFilter', # Сортировка
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', # Пагинация
'PAGE_SIZE': 10, # Размер страницы
}
Подключение DRF и дополнительных пакетов необходимо для использования функциональности API.
Настройки DRF определяют поведение API, включая аутентификацию, разрешения, фильтрацию и пагинацию.
JWT-аутентификация позволяет безопасно аутентифицировать пользователей с помощью токенов.
Пагинация улучшает производительность и удобство использования при работе с большим объемом данных.
Создадим сериализаторы для преобразования данных
Сериализаторы преобразуют данные моделей в форматы, пригодные для передачи по сети (например, JSON), и обратно.
Файл api/serializers.py
from rest_framework import serializers
from .models import Profile, Category, Post, Comment
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
"""Сериализатор для модели User"""
class Meta:
model = User
fields = ('id', 'username')
class ProfileSerializer(serializers.ModelSerializer):
"""Сериализатор для профиля пользователя"""
user = UserSerializer(read_only=True)
class Meta:
model = Profile
fields = ('id', 'user', 'bio')
class CommentSerializer(serializers.ModelSerializer):
"""Сериализатор для комментария"""
author = UserSerializer(read_only=True)
class Meta:
model = Comment
fields = ('id', 'author', 'text', 'created')
class PostSerializer(serializers.ModelSerializer):
"""Сериализатор для поста"""
author = UserSerializer(read_only=True)
comments = CommentSerializer(many=True, read_only=True)
category = serializers.SlugRelatedField(slug_field='name', queryset=Category.objects.all())
likes_count = serializers.SerializerMethodField()
class Meta:
model = Post
fields = ('id', 'author', 'title', 'content', 'category', 'published', 'updated', 'comments', 'likes_count')
def get_likes_count(self, obj):
return obj.likes.count()
class CategorySerializer(serializers.ModelSerializer):
"""Сериализатор для категории"""
posts = PostSerializer(many=True, read_only=True)
class Meta:
model = Category
fields = ('id', 'name', 'description', 'posts')
Сериализаторы обеспечивают преобразование сложных типов данных в формат JSON для передачи по API.
Они также обрабатывают валидацию данных при создании или обновлении объектов.
Вложенные сериализаторы позволяют включать связанные данные в ответы API.
Реализуем представления и маршруты для обработки запросов
в файле api/views.py
Разработаем PostViewSet
с использованием filter_backends
, filterset_fields
, search_fields
и ordering_fields
где настраиваем фильтрацию, поиск и сортировку
from rest_framework import viewsets, permissions, filters
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Profile, Category, Post, Comment
from .serializers import ProfileSerializer, CategorySerializer, PostSerializer, CommentSerializer
from .permissions import IsOwnerOrReadOnly
class ProfileViewSet(viewsets.ModelViewSet):
"""Представление для профилей пользователей"""
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(user=self.request.user)
class CategoryViewSet(viewsets.ModelViewSet):
"""Представление для категорий"""
queryset = Category.objects.all()
serializer_class = CategorySerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
class PostViewSet(viewsets.ModelViewSet):
"""Представление для постов"""
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [IsOwnerOrReadOnly]
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
filterset_fields = ['category__name', 'author__username']
search_fields = ['title', 'content']
ordering_fields = ['published', 'author__username', 'likes_count']
def perform_create(self, serializer):
serializer.save(author=self.request.user)
@action(detail=True, methods=['post'])
def like(self, request, pk=None):
"""Пользователь может лайкнуть пост"""
post = self.get_object()
post.likes.add(request.user)
return Response({'status': 'пост лайкнут'})
@action(detail=True, methods=['post'])
def unlike(self, request, pk=None):
"""Пользователь может убрать лайк с поста"""
post = self.get_object()
post.likes.remove(request.user)
return Response({'status': 'лайк с поста убран'})
class CommentViewSet(viewsets.ModelViewSet):
"""Представление для комментариев"""
queryset = Comment.objects.all()
serializer_class = CommentSerializer
permission_classes = [IsOwnerOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
Фильтрация позволяет получать данные по определенным критериям.
Поиск облегчает нахождение информации по ключевым словам.
Сортировка позволяет организовывать данные в определенном порядке.
в файле api/permissions.py
создадим пользовательское разрешение
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Пользовательское разрешение, позволяющее владельцам объекта редактировать его. """
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.author == request.user
Пользовательские разрешения позволяют настроить доступ к ресурсам в соответствии с бизнес-логикой.
В данном случае, только автор поста или комментария может его редактировать или удалять.
в файле api/urls.py
настроим маршруты для API
from django.urls import include, path
from rest_framework import routers
from .views import ProfileViewSet, CategoryViewSet, PostViewSet, CommentViewSet
router = routers.DefaultRouter()
router.register(r'profiles', ProfileViewSet)
router.register(r'categories', CategoryViewSet)
router.register(r'posts', PostViewSet)
router.register(r'comments', CommentViewSet)
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
И в файле Site/urls.py
добавим пути для JWT-аутентификации
from django.contrib import admin
from django.urls import path, include
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('myapi.urls')),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
Маршрутизация определяет, какие URL-адреса обрабатывают определенные представления.
JWT-аутентификация требует эндпоинтов для получения и обновления токенов.
Протестируем наше API
Для тестирования получим JWT-токен. Для этого необходимо отправить POST-запрос на /api/token/
с именем пользователя и паролем.
Где name
и password
ваши данные.
`curl -X POST -d "username=name&password=your_password" http://127.0.0.1:8000/api/token/`
Выходные данные:
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}
Теперь мы будем использовать полученный access
токен для доступа к защищенным ресурсам, таким как список постов.
Входные данные:
Заголовок
Authorization
со значениемBearer your_access_token
, гдеyour_access_token
— это токен, полученный на предыдущем шаге.
Команда cURL:
curl -H "Authorization: Bearer your_access_token" http://127.0.0.1:8000/posts/
Выходные данные:
[
{
"id": 1,
"author": {
"id": 1,
"username": "alex"
},
"title": "Первый пост",
"content": "Это содержимое первого поста.",
"category": "Django",
"published": "2023-10-29T12:00:00Z",
"updated": "2023-10-29T12:00:00Z",
"comments": [],
"likes_count": 0
}
]
Здесь сервер проверяет токен в заголовке запроса и, если он действителен, возвращает запрошенные данные.
Теперь протестируем основные функции нашего API: создание поста, лайк поста и добавление комментария.
Создадим пост
Входные данные:
-
Заголовки:
Content-Type: application/json
Authorization: Bearer your_access_token
Тело запроса:
"title": "Новый пост",
"content": "Содержимое нового поста.",
"category": "Django"
Команда cURL:
curl -X POST -H "Content-Type: application/json" \
-H "Authorization: Bearer your_access_token" \
-d '{"title": "Новый пост", "content": "Содержимое нового поста.", "category": "Django"}' \
http://127.0.0.1:8000/posts/
Выходные данные:
"id": 2,
"author": {
"id": 1,
"username": "alex"
},
"title": "Новый пост",
"content": "Содержимое нового поста.",
"category": "Django",
"published": "2024-10-29T12:30:00Z",
"updated": "2024-10-29T12:30:00Z",
"comments": [],
"likes_count": 0
Лайк поста
Входные данные:
Заголовок
Authorization: Bearer your_access_token
URL содержит идентификатор поста, который мы хотим лайкнуть (например,
2
).
Команда cURL:
curl -X POST -H "Authorization: Bearer your_access_token" \
http://127.0.0.1:8000/posts/2/like/
Выходные данные:
{
"status": "пост лайкнут"
}
Добавим комментарий
Входные данные:
-
Заголовки:
Content-Type: application/json
Authorization: Bearer your_access_token
Тело запроса:
{
"post": 2,
"text": "Отличный пост!"
}
Команда cURL:
curl -X POST -H "Content-Type: application/json" \
-H "Authorization: Bearer your_access_token" \
-d '{"post": 2, "text": "Отличный пост!"}' \
http://127.0.0.1:8000/comments/
Выходные данные:
{
"id": 1,
"author": {
"id": 1,
"username": "alex"
},
"text": "Отличный пост!",
"created": "2024-10-29T12:35:00Z"
}
Деплой на сервера Amvera
После успешного тестирования нашего API на локальном сервере, давай развернём его на удалённом сервере, чтобы он был доступен 24/7 и не зависел от вашего компьютера.
Регистрация в сервисе Amvera Cloud
-
Создай аккаунт:
Перейди на сайт Amvera.
Нажми на кнопку "Регистрация".
Заполни все необходимые поля, включая номер телефона, и нажми "Отправить код".
Введи код из SMS, подтверди, что ты не робот, и нажми "Регистрация".
Подтверди адрес электронной почты, перейдя по ссылке в письме.
Создание проекта и размещение приложения
-
Создай новый проект:
После входа на платформу, на главной странице нажми кнопку "Создать" или "Создать первый!".
2.Настрой проект:
Присвой проекту название (лучше на английском).
Выбери тарифный план. Для развертывания нашего APi достаточно самого простого тарифа.
Подготовка кода для развертывания:
Amvera использует git для доставки кода в облако. Вам потребуется создать файл конфигурации
amvera.yml
, который подскажет облаку, как запускать ваш проект.-
Для упрощения создания этого файла воспользуйтесь графическим инструментом генерации.
-
Выбор окружения и зависимостей:
Укажите версию Python и путь до файла
requirements.txt
, который содержит все необходимые пакеты.Укажите путь до основного файла вашего проекта, например
main.py
.
-
Генерация и загрузка файла:
Нажмите "Generate YAML" для создания файла
amvera.yml
и загрузите его в корень вашего проекта.
Файл конфигурации amvera.yml
служит для того, чтобы платформа Amvera знала, как правильно собрать и запустить ваш проект. Этот файл содержит ключевую информацию об окружении, зависимостях, а также инструкциях для запуска приложения.
Структура файла amvera.yml
:
meta:
environment: python
toolchain:
name: pip
version: 3.8
build:
requirementsPath: requirements.txt
run:
scriptName: manage.py
persistenceMount: /data
containerPort: 8000
Для того чтобы наш проект корректно работал в среде Amvera, важно указать все необходимые пакеты в файле requirements.txt
. Этот файл определяет все зависимости Python, которые нужны для выполнения кода.
Вот так выглядит наш файл requirements.txt
:
Django==3.2.9
djangorestframework==3.12.4
django-filter==2.4.0
djangorestframework-simplejwt==4.7.2
Инициализация и отправка проекта в репозиторий:
-
Инициализируйте git репозиторий в корне вашего проекта, если это еще не сделано:
git init
-
Привяжите локальный репозиторий к удаленному на Amvera:
git remote add amvera
-
Добавьте и зафиксируйте изменения:
git add . git commit -m "Initial commit"
-
Отправьте проект в облако:
git push amvera master
Сборка и развертывание проекта:
После отправки проекта в систему, на странице проекта статус изменится на "Выполняется сборка". После завершения сборки проект перейдет в стадию "Выполняется развертывание", а затем в статус "Успешно развернуто".
Если проект не развернулся, проверьте логи сборки и логи приложения для отладки.
Если проект завис на этапе "Сборка", убедитесь в корректности файла
amvera.yml
Поздравляю! Мы создали приложение REST API с использованием Django и Django REST Framework.
Если вам требуется легко развернуть проект на сервере и доставлять в него обновления тремя командами в IDE, зарегистрируйтесь в облаке со встроенным CI/CD Amvera Cloud, и получите 111 руб. на тестирование функционала.
danilovmy
нормальный restapi имеет документацию в стиле Json/yaml для автоматизаций плюс не обязательно но желательно визуализацию для разработчиков. Я не очень понимаю, как это решено в примерах из статьи.