В современном мире веб-разработки 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_backendsfilterset_fieldssearch_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

  1. Создай аккаунт:

    • Перейди на сайт Amvera.

    • Нажми на кнопку "Регистрация".

    • Заполни все необходимые поля, включая номер телефона, и нажми "Отправить код".

    • Введи код из SMS, подтверди, что ты не робот, и нажми "Регистрация".

    • Подтверди адрес электронной почты, перейдя по ссылке в письме.

Создание проекта и размещение приложения

  1. Создай новый проект:

    • После входа на платформу, на главной странице нажми кнопку "Создать" или "Создать первый!".

    https://habrastorage.org/r/w1560/getpro/habr/upload_files/90a/00c/aa0/90a00caa0d20e5d56d26f898a5eb9fe2.png
    Создание проекта

2.Настрой проект:

  • Присвой проекту название (лучше на английском).

  • Выбери тарифный план. Для развертывания нашего APi достаточно самого простого тарифа.

  1. Подготовка кода для развертывания:

  • 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 руб. на тестирование функционала.

Комментарии (1)


  1. danilovmy
    19.11.2024 11:48

    нормальный restapi имеет документацию в стиле Json/yaml для автоматизаций плюс не обязательно но желательно визуализацию для разработчиков. Я не очень понимаю, как это решено в примерах из статьи.