Привет! Django REST Framework (DRF) — это мощный и гибкий инструмент для создания веб-API на Django. Он предоставляет готовые решения для сериализации, аутентификации, маршрутизации и многого другого.
В этой статье мы создадим полноценное REST API для управления книгами.
В этой статье мы разберём:
- Установку и настройку DRF
- Создание моделей
- Сериализаторы
- ViewSet и маршрутизацию
- Browsable API
- Что нужно знать перед началом
- Основная часть
- Установка и настройка
- Создание моделей
- Создание сериализаторов
- Создание ViewSet
- Маршрутизация
- Запуск и тестирование
- Полный код
- Задачи для закрепления
- Нюансы и подводные камни
- Частые ошибки и как их избежать
- Ошибка 1: Забыл добавить приложение в INSTALLED_APPS
- Ошибка 2: Неправильная настройка URL
- Ошибка 3: Неоптимизированные запросы
- Шпаргалка
- Заключение
- КВИЗ
- Что дальше?
Что нужно знать перед началом
Для этого урока тебе понадобится:
Совет: DRF автоматически генерирует удобную документацию — Browsable API, доступную прямо в браузере.
Основная часть
Установка и настройка
Шаг 1. Установка DRF:
pip install djangorestframeworkШаг 2. Добавь приложение в settings.py:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
# ...
'rest_framework', # Добавляем DRF
'books', # Наше приложение
]Шаг 3. Создание проекта и приложения:
django-admin startproject bookstore
cd bookstore
python manage.py startapp booksШаг 4. Настройка DRF (опционально):
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
}Совет:
IsAuthenticatedOrReadOnlyпозволяет читать данные без авторизации, но требует входа для создания/изменения.
Создание моделей
from django.db import models
from django.contrib.auth.models import User
class Author(models.Model):
name = models.CharField(max_length=200)
bio = models.TextField(blank=True)
birth_date = models.DateField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=300)
author = models.ForeignKey(
Author,
on_delete=models.CASCADE,
related_name='books'
)
isbn = models.CharField(max_length=13, unique=True)
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
published_date = models.DateField()
in_stock = models.BooleanField(default=True)
created_by = models.ForeignKey(
User,
on_delete=models.SET_NULL,
null=True,
related_name='created_books'
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f"{self.title} by {self.author.name}"
class Review(models.Model):
book = models.ForeignKey(
Book,
on_delete=models.CASCADE,
related_name='reviews'
)
user = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='reviews'
)
rating = models.IntegerField(choices=[(i, i) for i in range(1, 6)])
comment = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = ['book', 'user'] # Один отзыв на пользователя на книгу
def __str__(self):
return f"Review by {self.user.username}"Миграции:
python manage.py makemigrations
python manage.py migrateСоздание сериализаторов
Сериализаторы преобразуют модели в JSON и обратно.
from rest_framework import serializers
from django.contrib.auth.models import User
from .models import Author, Book, Review
class AuthorSerializer(serializers.ModelSerializer):
book_count = serializers.SerializerMethodField()
class Meta:
model = Author
fields = ['id', 'name', 'bio', 'birth_date', 'book_count', 'created_at']
read_only_fields = ['created_at']
def get_book_count(self, obj):
return obj.books.count()
class ReviewSerializer(serializers.ModelSerializer):
user = serializers.StringRelatedField(read_only=True)
class Meta:
model = Review
fields = ['id', 'book', 'user', 'rating', 'comment', 'created_at']
read_only_fields = ['user', 'created_at']
class BookListSerializer(serializers.ModelSerializer):
author_name = serializers.CharField(source='author.name', read_only=True)
class Meta:
model = Book
fields = ['id', 'title', 'author_name', 'price', 'in_stock']
class BookDetailSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
author_id = serializers.PrimaryKeyRelatedField(
queryset=Author.objects.all(),
source='author',
write_only=True
)
reviews = ReviewSerializer(many=True, read_only=True)
created_by = serializers.StringRelatedField(read_only=True)
class Meta:
model = Book
fields = [
'id', 'title', 'author', 'author_id', 'isbn',
'description', 'price', 'published_date', 'in_stock',
'reviews', 'created_by', 'created_at', 'updated_at'
]
read_only_fields = ['created_by', 'created_at', 'updated_at']
def validate_isbn(self, value):
clean_isbn = value.replace('-', '')
if len(clean_isbn) not in [10, 13]:
raise serializers.ValidationError("ISBN must be 10 or 13 digits")
if not clean_isbn.isdigit():
raise serializers.ValidationError("ISBN must contain only digits")
return clean_isbn
def validate_price(self, value):
if value <= 0:
raise serializers.ValidationError("Price must be greater than zero")
return valueСовет:
ModelSerializerавтоматически генерирует поля из модели — это экономит много времени.
Создание ViewSet
ViewSet объединяет все CRUD-операции в одном классе.
from rest_framework import viewsets, filters, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
from django_filters.rest_framework import DjangoFilterBackend
from .models import Author, Book, Review
from .serializers import (
AuthorSerializer,
BookListSerializer,
BookDetailSerializer,
ReviewSerializer
)
class AuthorViewSet(viewsets.ModelViewSet):
queryset = Author.objects.all()
serializer_class = AuthorSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [filters.SearchFilter, filters.OrderingFilter]
search_fields = ['name', 'bio']
ordering_fields = ['name', 'created_at']
@action(detail=True, methods=['get'])
def books(self, request, pk=None):
author = self.get_object()
books = author.books.all()
serializer = BookListSerializer(books, many=True)
return Response(serializer.data)
class BookViewSet(viewsets.ModelViewSet):
queryset = Book.objects.select_related('author').prefetch_related('reviews')
permission_classes = [IsAuthenticatedOrReadOnly]
filter_backends = [
DjangoFilterBackend,
filters.SearchFilter,
filters.OrderingFilter
]
filterset_fields = ['author', 'in_stock']
search_fields = ['title', 'description', 'isbn']
ordering_fields = ['title', 'price', 'published_date']
def get_serializer_class(self):
if self.action == 'list':
return BookListSerializer
return BookDetailSerializer
def perform_create(self, serializer):
serializer.save(created_by=self.request.user)
@action(detail=True, methods=['post'], permission_classes=[IsAuthenticated])
def add_review(self, request, pk=None):
book = self.get_object()
if Review.objects.filter(book=book, user=request.user).exists():
return Response(
{'error': 'You have already reviewed this book'},
status=status.HTTP_400_BAD_REQUEST
)
serializer = ReviewSerializer(data=request.data)
if serializer.is_valid():
serializer.save(book=book, user=request.user)
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)Совет:
ModelViewSetавтоматически предоставляетlist,create,retrieve,update,partial_update,destroy.
Маршрутизация
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register(r'authors', views.AuthorViewSet)
router.register(r'books', views.BookViewSet)
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]bookstore/urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('books.urls')),
]Совет:
DefaultRouterавтоматически создаёт все нужные маршруты для ViewSet`а.
Запуск и тестирование
Запуск сервера:
python manage.py runserverТестирование через Browsable API:
Открой в браузере: http://127.0.0.1:8000/api/
Ты увидишь интерактивную документацию, где можно тестировать API прямо из браузера.
# GET — список книг
curl -H 'Accept: application/json' http://127.0.0.1:8000/api/books/
# POST — создание книги
curl -X POST -d '{"title":"New Book","price":29.99}' -H 'Content-Type: application/json' http://127.0.0.1:8000/api/books/Полный код
bookstore/settings.py (добавления):
INSTALLED_APPS = [
# ...
'rest_framework',
'books',
]
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20,
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
}Структура проекта:
bookstore/
├── manage.py
├── bookstore/
│ ├── settings.py
│ └── urls.py
└── books/
├── models.py
├── serializers.py
├── views.py
├── urls.py
└── admin.pyЗадачи для закрепления
Задача 1. Создай модель Category и свяжи её с Book.
Задача 2. Добавь фильтрацию книг по категории.
Задача 3. Создай кастомный эндпоинт /books/search/?q=query.
Задача 4. Настрой пагинацию (10 книг на страницу).
Задача 5. Добавь автодокументацию для всех эндпоинтов.
Ответы:
Задача 1.
class Category(models.Model):
name = models.CharField(max_length=100)
# В Book добавляем:
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)Задача 2.
filterset_fields = ['category', 'in_stock']Задача 3.
@action(detail=False, methods=['get'])
def search(self, request):
q = request.query_params.get('q', '')
books = self.get_queryset().filter(title__icontains=q)
serializer = self.get_serializer(books, many=True)
return Response(serializer.data)Задача 4.
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
}Задача 5.
# DRF автоматически создаёт документацию по /api/
# Для кастомных эндпоинтов добавляем description:
@action(detail=False, methods=['get'], description="Поиск книг по названию")Нюансы и подводные камни
ModelSerializerавтоматически создаёт поля из модели.ModelViewSetпредоставляет все CRUD-операции.DefaultRouterавтоматически создаёт маршруты для ViewSet.@action— для кастомных эндпоинтов внутри ViewSet.Browsable APIдоступен в браузере по адресу/api/.permission_classesконтролируют доступ.filter_backends— для фильтрации, поиска, сортировки.select_relatedиprefetch_relatedоптимизируют запросы к БД.
Частые ошибки и как их избежать
Ошибка 1: Забыл добавить приложение в INSTALLED_APPS
Добавь 'rest_framework' и своё приложение.
Ошибка 2: Неправильная настройка URL
Используй DefaultRouter или SimpleRouter.
Ошибка 3: Неоптимизированные запросы
Используй select_related и prefetch_related для внешних ключей.
Шпаргалка
Заключение
Сегодня мы:
- Установили и настроили Django REST Framework
- Создали модели, сериализаторы и ViewSet
- Настроили маршрутизацию
- Протестировали через Browsable API








