API endpoint в Django REST Framework
Django REST Framework (DRF) - это мощный инструмент для создания REST API в Django приложениях. Он предоставляет гибкие сериализаторы, ViewSets, роутеры и множество других возможностей для быстрой разработки API.
Установка и настройка
Сначала установи DRF в свой проект:
1pip install djangorestframework
Добавь в INSTALLED_APPS:
Модель данных
Создадим простую модель для примера:
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 для обратной совместимости