Я расскажу, как создать функциональный и масштабируемый REST API для интернет-магазина с использованием Django и Django REST Framework (DRF). Эта статья ориентирована на разработчиков, которые хотят освоить создание API с учетом лучших практик и профессионального подхода. Мы реализуем API для управления товарами, категориями и заказами, а также добавим базовую авторизацию. Все шаги подробно описаны, чтобы вы могли легко их повторить.
Подготовка окружения
Для начала я предполагаю, что у вас установлен Python (версия 3.8 или выше). Мы будем использовать виртуальное окружение для изоляции зависимостей, что является стандартом в профессиональной разработке.
- Создаем виртуальное окружение:python -m venv venv
source venv/bin/activate # Для Windows: venv\Scripts\activate - Устанавливаем зависимости:pip install django djangorestframework
- Создаем Django-проект:django-admin startproject store_api
cd store_api - Создаем приложение:python manage.py startapp products
Добавьте приложения products и rest_framework в INSTALLED_APPS в файле store_api/settings.py:
INSTALLED_APPS = [
...
'rest_framework',
'products',
]
Модели данных
Для нашего магазина нужны модели для категорий, товаров и заказов. Я создам их с учетом типичных требований интернет-магазина, обеспечивая гибкость и масштабируемость.
Откройте файл products/models.py и добавьте следующий код:
from django.db import models
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
description = models.TextField(blank=True)
class Meta:
verbose_name_plural = "categories"
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='products')
stock = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Order(models.Model):
STATUS_CHOICES = (
('pending', 'Pending'),
('completed', 'Completed'),
('canceled', 'Canceled'),
)
user = models.ForeignKey('auth.User', on_delete=models.CASCADE, related_name='orders')
created_at = models.DateTimeField(auto_now_add=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0.00)
def __str__(self):
return f"Order {self.id} by {self.user.username}"
class OrderItem(models.Model):
order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='items')
product = models.ForeignKey(Product, on_delete=models.CASCADE)
quantity = models.PositiveIntegerField()
price = models.DecimalField(max_digits=10, decimal_places=2)
def __str__(self):
return f"{self.quantity} x {self.product.name}"
Я выбрал DecimalField для цен, чтобы избежать проблем с точностью чисел с плавающей точкой. Поле stock отслеживает остатки, а модель OrderItem связывает заказы с товарами, фиксируя количество и цену на момент покупки.
Примените миграции:
python manage.py makemigrations
python manage.py migrate
Сериализаторы
Сериализаторы в DRF позволяют преобразовывать данные в JSON и обратно. Я настрою их так, чтобы API был удобным и производительным.
Создайте файл products/serializers.py:
from rest_framework import serializers
from .models import Category, Product, Order, OrderItem
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name', 'description']
class ProductSerializer(serializers.ModelSerializer):
category = CategorySerializer(read_only=True)
category_id = serializers.PrimaryKeyRelatedField(
queryset=Category.objects.all(), source='category', write_only=True
)
class Meta:
model = Product
fields = ['id', 'name', 'description', 'price', 'category', 'category_id', 'stock', 'created_at']
class OrderItemSerializer(serializers.ModelSerializer):
product = ProductSerializer(read_only=True)
product_id = serializers.PrimaryKeyRelatedField(
queryset=Product.objects.all(), source='product', write_only=True
)
class Meta:
model = OrderItem
fields = ['id', 'product', 'product_id', 'quantity', 'price']
class OrderSerializer(serializers.ModelSerializer):
items = OrderItemSerializer(many=True, read_only=True)
user = serializers.StringRelatedField(read_only=True)
class Meta:
model = Order
fields = ['id', 'user', 'created_at', 'status', 'total_price', 'items']
Я разделил поля category и category_id (аналогично для product и product_id), чтобы упростить чтение и запись данных. Это улучшает производительность и делает API интуитивно понятным.
Представления (Viewsets)
Для обработки запросов я использую ViewSet’ы, которые предоставляют готовые методы для CRUD-операций. Это экономит время и упрощает поддержку кода.
Создайте файл products/views.py:
from rest_framework import viewsets, permissions
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import Category, Product, Order, OrderItem
from .serializers import CategorySerializer, ProductSerializer, OrderSerializer, OrderItemSerializer
class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.all()
serializer_class = ProductSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
@action(detail=False, methods=['get'])
def low_stock(self, request):
threshold = int(request.query_params.get('threshold', 10))
low_stock_products = self.get_queryset().filter(stock__lte=threshold)
serializer = self.get_serializer(low_stock_products, many=True)
return Response(serializer.data)
class OrderViewSet(viewsets.ModelViewSet):
serializer_class = OrderSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return Order.objects.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Я добавил метод low_stock для получения товаров с низким запасом — это полезно для управления складом. Заказы доступны только аутентифицированным пользователям, чтобы обеспечить безопасность данных.
Маршруты
Настроим маршруты для API. Создайте файл products/urls.py:
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import CategoryViewSet, ProductViewSet, OrderViewSet
router = DefaultRouter()
router.register(r'categories', CategoryViewSet)
router.register(r'products', ProductViewSet)
router.register(r'orders', OrderViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Добавьте маршруты в store_api/urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('products.urls')),
]
Авторизация
Для защиты API я использую токены JWT. Настройте settings.py:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
],
}
Установите djangorestframework-simplejwt:
pip install djangorestframework-simplejwt
Добавьте маршруты для токенов в <-code>store_api/urls.py:
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('products.urls')),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
Теперь пользователи могут получать токены через /api/token/.
Тестирование API
Запустите сервер:
python manage.py runserver
Примеры запросов (используйте Postman или cURL):
- Получить список товаров:GET http://localhost:8000/api/products/
- Создать заказ (с токеном в заголовке Authorization: Bearer <token>):POST http://localhost:8000/api/orders/
{
"items": [
{"product_id": 1, "quantity": 2, "price": 10.00}
],
"status": "pending"
} - Получить товары с низким запасом:GET http://localhost:8000/api/products/low_stock/?threshold=5
Оптимизация и лучшие практики
- Производительность: Используйте select_related для оптимизации запросов:class ProductViewSet(viewsets.ModelViewSet):
queryset = Product.objects.select_related('category').all() - Валидация: Добавьте проверки в сериализаторы, чтобы, например, stock не становился отрицательным.
- Документация: Используйте drf-spectacular для генерации OpenAPI-документации:pip install drf-spectacular
Добавьте в settings.py:REST_FRAMEWORK = {
...
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
Настройте маршрут в store_api/urls.py:from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
urlpatterns = [
...
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
]
Документация будет доступна по адресу /api/docs/.
Заключение
Я показал, как создать простой, но мощный API для интернет-магазина на Django. Этот подход легко масштабируется: вы можете добавить пагинацию, фильтрацию, поиск или интеграцию с внешними сервисами. Следуя этим шагам, вы получите надежное решение, готовое к использованию в реальных проектах.