API endpoint в Django REST Framework

Django REST Framework (DRF) - это мощный инструмент для создания REST API в Django приложениях. Он предоставляет гибкие сериализаторы, ViewSets, роутеры и множество других возможностей для быстрой разработки API.

Установка и настройка

Сначала установи DRF в свой проект:

1pip install djangorestframework

Добавь в INSTALLED_APPS:

1INSTALLED_APPS = [
2    # ... другие приложения
3    'rest_framework',
4]

Модель данных

Создадим простую модель для примера:

 1from django.db import models
 2from django.contrib.auth.models import User
 3
 4class Book(models.Model):
 5    title = models.CharField(max_length=200, verbose_name='Название')
 6    author = models.CharField(max_length=200, verbose_name='Автор')
 7    description = models.TextField(blank=True, verbose_name='Описание')
 8    price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='Цена')
 9    published_date = models.DateField(verbose_name='Дата публикации')
10    is_available = models.BooleanField(default=True, verbose_name='Доступна')
11    created_by = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='Создатель')
12    created_at = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
13    updated_at = models.DateTimeField(auto_now=True, verbose_name='Дата обновления')
14
15    class Meta:
16        verbose_name = 'Книга'
17        verbose_name_plural = 'Книги'
18        ordering = ['-created_at']
19
20    def __str__(self):
21        return f"{self.title} - {self.author}"

Сериализатор

Сериализатор преобразует модели Django в JSON и обратно:

 1from rest_framework import serializers
 2from .models import Book
 3from django.contrib.auth.models import User
 4
 5class UserSerializer(serializers.ModelSerializer):
 6    class Meta:
 7        model = User
 8        fields = ['id', 'username', 'email', 'first_name', 'last_name']
 9
10class BookSerializer(serializers.ModelSerializer):
11    created_by = UserSerializer(read_only=True)
12    author_full_name = serializers.SerializerMethodField()
13
14    class Meta:
15        model = Book
16        fields = [
17            'id', 'title', 'author', 'description', 'price',
18            'published_date', 'is_available', 'created_by',
19            'created_at', 'updated_at', 'author_full_name'
20        ]
21        read_only_fields = ['created_by', 'created_at', 'updated_at']
22
23    def get_author_full_name(self, obj):
24        return f"{obj.author} ({obj.published_date.year})"
25
26    def validate_price(self, value):
27        if value <= 0:
28            raise serializers.ValidationError("Цена должна быть больше нуля")
29        return value
30
31    def validate_title(self, value):
32        if len(value.strip()) < 3:
33            raise serializers.ValidationError("Название должно содержать минимум 3 символа")
34        return value.strip()

API Views

Создание представлений для API:

APIView - для сложной логики

 1from rest_framework.views import APIView
 2from rest_framework.response import Response
 3from rest_framework import status
 4from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
 5from django.shortcuts import get_object_or_404
 6from .models import Book
 7from .serializers import BookSerializer
 8
 9class BookListAPIView(APIView):
10    permission_classes = [IsAuthenticatedOrReadOnly]
11
12    def get(self, request):
13        books = Book.objects.all()
14        serializer = BookSerializer(books, many=True)
15        return Response(serializer.data)
16
17    def post(self, request):
18        serializer = BookSerializer(data=request.data)
19        if serializer.is_valid():
20            serializer.save(created_by=request.user)
21            return Response(serializer.data, status=status.HTTP_201_CREATED)
22        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
23
24class BookDetailAPIView(APIView):
25    permission_classes = [IsAuthenticatedOrReadOnly]
26
27    def get_object(self, pk):
28        return get_object_or_404(Book, pk=pk)
29
30    def get(self, request, pk):
31        book = self.get_object(pk)
32        serializer = BookSerializer(book)
33        return Response(serializer.data)
34
35    def put(self, request, pk):
36        book = self.get_object(pk)
37        serializer = BookSerializer(book, data=request.data)
38        if serializer.is_valid():
39            serializer.save()
40            return Response(serializer.data)
41        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
42
43    def delete(self, request, pk):
44        book = self.get_object(pk)
45        book.delete()
46        return Response(status=status.HTTP_204_NO_CONTENT)

ViewSet - для стандартных CRUD операций

 1from rest_framework import viewsets
 2from rest_framework.permissions import IsAuthenticatedOrReadOnly
 3from rest_framework.filters import SearchFilter, OrderingFilter
 4from django_filters.rest_framework import DjangoFilterBackend
 5from .models import Book
 6from .serializers import BookSerializer
 7
 8class BookViewSet(viewsets.ModelViewSet):
 9    queryset = Book.objects.all()
10    serializer_class = BookSerializer
11    permission_classes = [IsAuthenticatedOrReadOnly]
12    filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
13    search_fields = ['title', 'author', 'description']
14    ordering_fields = ['title', 'author', 'price', 'published_date', 'created_at']
15    filterset_fields = ['is_available', 'created_by']
16
17    def perform_create(self, serializer):
18        serializer.save(created_by=self.request.user)
19
20    def get_queryset(self):
21        queryset = Book.objects.all()
22        # Фильтрация по цене
23        min_price = self.request.query_params.get('min_price', None)
24        max_price = self.request.query_params.get('max_price', None)
25
26        if min_price is not None:
27            queryset = queryset.filter(price__gte=min_price)
28        if max_price is not None:
29            queryset = queryset.filter(price__lte=max_price)
30
31        return queryset

Роутеры

Роутеры автоматически создают URL паттерны для ViewSets:

 1from rest_framework.routers import DefaultRouter
 2from django.urls import path, include
 3from .views import BookViewSet, BookListAPIView, BookDetailAPIView
 4
 5# Создание роутера для ViewSet
 6router = DefaultRouter()
 7router.register(r'books', BookViewSet)
 8
 9# URL паттерны
10urlpatterns = [
11    # Автоматические URL от роутера
12    path('api/', include(router.urls)),
13
14    # Ручные URL для APIView
15    path('api/books-manual/', BookListAPIView.as_view(), name='book-list-manual'),
16    path('api/books-manual/<int:pk>/', BookDetailAPIView.as_view(), name='book-detail-manual'),
17]

Аутентификация и разрешения

Настройка безопасности для API:

 1from rest_framework.authentication import TokenAuthentication, SessionAuthentication
 2from rest_framework.permissions import IsAuthenticated, IsAdminUser, AllowAny
 3from rest_framework.throttling import UserRateThrottle, AnonRateThrottle
 4
 5# В settings.py
 6REST_FRAMEWORK = {
 7    'DEFAULT_AUTHENTICATION_CLASSES': [
 8        'rest_framework.authentication.TokenAuthentication',
 9        'rest_framework.authentication.SessionAuthentication',
10    ],
11    'DEFAULT_PERMISSION_CLASSES': [
12        'rest_framework.permissions.IsAuthenticatedOrReadOnly',
13    ],
14    'DEFAULT_THROTTLE_CLASSES': [
15        'rest_framework.throttling.AnonRateThrottle',
16        'rest_framework.throttling.UserRateThrottle'
17    ],
18    'DEFAULT_THROTTLE_RATES': {
19        'anon': '100/hour',
20        'user': '1000/hour'
21    },
22    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
23    'PAGE_SIZE': 20
24}

Кастомные разрешения

 1from rest_framework import permissions
 2
 3class IsOwnerOrReadOnly(permissions.BasePermission):
 4    """
 5    Разрешение только владельцу объекта редактировать его
 6    """
 7    def has_object_permission(self, request, view, obj):
 8        # Чтение разрешено всем
 9        if request.method in permissions.SAFE_METHODS:
10            return True
11
12        # Запись разрешена только владельцу
13        return obj.created_by == request.user
14
15class BookViewSet(viewsets.ModelViewSet):
16    queryset = Book.objects.all()
17    serializer_class = BookSerializer
18    permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
19    # ... остальные настройки

Пагинация

Настройка пагинации для больших списков:

 1from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination
 2
 3class BookPagination(PageNumberPagination):
 4    page_size = 10
 5    page_size_query_param = 'page_size'
 6    max_page_size = 100
 7    page_query_param = 'page'
 8
 9class BookViewSet(viewsets.ModelViewSet):
10    queryset = Book.objects.all()
11    serializer_class = BookSerializer
12    pagination_class = BookPagination
13    # ... остальные настройки

Фильтрация и поиск

Расширенная фильтрация с django-filter:

 1import django_filters
 2from rest_framework import filters
 3
 4class BookFilter(django_filters.FilterSet):
 5    title = django_filters.CharFilter(lookup_expr='icontains')
 6    author = django_filters.CharFilter(lookup_expr='icontains')
 7    min_price = django_filters.NumberFilter(field_name='price', lookup_expr='gte')
 8    max_price = django_filters.NumberFilter(field_name='price', lookup_expr='lte')
 9    published_after = django_filters.DateFilter(field_name='published_date', lookup_expr='gte')
10    published_before = django_filters.DateFilter(field_name='published_date', lookup_expr='lte')
11
12    class Meta:
13        model = Book
14        fields = ['is_available', 'created_by']
15
16class BookViewSet(viewsets.ModelViewSet):
17    queryset = Book.objects.all()
18    serializer_class = BookSerializer
19    filterset_class = BookFilter
20    search_fields = ['title', 'author', 'description']
21    ordering_fields = ['title', 'author', 'price', 'published_date']
22    # ... остальные настройки

Тестирование API

Создание тестов для API endpoints:

 1from django.test import TestCase
 2from django.urls import reverse
 3from rest_framework.test import APITestCase
 4from rest_framework import status
 5from django.contrib.auth.models import User
 6from .models import Book
 7from .serializers import BookSerializer
 8
 9class BookAPITestCase(APITestCase):
10    def setUp(self):
11        self.user = User.objects.create_user(
12            username='testuser',
13            email='test@example.com',
14            password='testpass123'
15        )
16        self.client.force_authenticate(user=self.user)
17
18        self.book = Book.objects.create(
19            title='Тестовая книга',
20            author='Тестовый автор',
21            description='Описание тестовой книги',
22            price=100.00,
23            published_date='2023-01-01',
24            created_by=self.user
25        )
26
27    def test_get_book_list(self):
28        url = reverse('book-list')
29        response = self.client.get(url)
30        self.assertEqual(response.status_code, status.HTTP_200_OK)
31        self.assertEqual(len(response.data['results']), 1)
32
33    def test_create_book(self):
34        url = reverse('book-list')
35        data = {
36            'title': 'Новая книга',
37            'author': 'Новый автор',
38            'description': 'Описание новой книги',
39            'price': 150.00,
40            'published_date': '2023-02-01'
41        }
42        response = self.client.post(url, data)
43        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
44        self.assertEqual(Book.objects.count(), 2)
45
46    def test_update_book(self):
47        url = reverse('book-detail', args=[self.book.pk])
48        data = {'title': 'Обновленная книга'}
49        response = self.client.patch(url, data)
50        self.assertEqual(response.status_code, status.HTTP_200_OK)
51        self.book.refresh_from_db()
52        self.assertEqual(self.book.title, 'Обновленная книга')

Документация API

Автоматическая генерация документации с drf-spectacular:

 1# Установка
 2pip install drf-spectacular
 3
 4# В settings.py
 5INSTALLED_APPS = [
 6    # ... другие приложения
 7    'drf_spectacular',
 8]
 9
10REST_FRAMEWORK = {
11    # ... другие настройки
12    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
13}
14
15SPECTACULAR_SETTINGS = {
16    'TITLE': 'Book API',
17    'DESCRIPTION': 'API для управления книгами',
18    'VERSION': '1.0.0',
19    'SERVE_INCLUDE_SCHEMA': False,
20}
21
22# В urls.py
23from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
24
25urlpatterns = [
26    # ... другие URL
27    path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
28    path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
29]

FAQ

Q: Когда использовать ViewSets, а когда APIView?
A: ViewSets удобны для стандартных CRUD операций и автоматически создают все необходимые endpoints. APIView лучше использовать для сложной бизнес-логики, когда нужен полный контроль над HTTP методами.

Q: Как добавить кастомную валидацию в сериализатор?
A: Используй методы validate_<field_name> для валидации отдельных полей или метод validate для валидации нескольких полей одновременно.

Q: Как ограничить количество запросов к API?
A: Используй throttling классы в REST_FRAMEWORK настройках. Можно настроить ограничения для анонимных пользователей и авторизованных пользователей.

Q: Как добавить фильтрацию по связанным объектам?
A: Используй django-filter с lookup_expr для связанных полей, например: author__username для фильтрации по имени пользователя автора.

Q: Как тестировать API endpoints?
A: Используй APITestCase из DRF, который предоставляет удобные методы для тестирования API, включая аутентификацию и создание тестовых данных.

Q: Как добавить кэширование для API?
A: Используй Django кэш фреймворк с декораторами или middleware, или настрой кэширование на уровне ViewSet с помощью cache_response декоратора.

Лучшие практики

  • Всегда используй сериализаторы для валидации входящих данных
  • Применяй соответствующие разрешения для каждого endpoint
  • Используй пагинацию для больших списков
  • Добавляй фильтрацию и поиск для удобства пользователей
  • Тестируй все API endpoints
  • Документируй API с помощью автоматических инструментов
  • Используй throttling для защиты от злоупотреблений
  • Применяй версионирование API для обратной совместимости