✅ Решения практикума 8

⚡ Все решения — итог

# Паттерн 1: ModelViewSet (Category, Supplier, Address)
class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

router = DefaultRouter()
router.register(r'categories', CategoryViewSet)

# Паттерн 2: Generic Views с get_serializer_class (Product, Customer...)
class ProductListCreateView(ListCreateAPIView):
    queryset = Product.objects.all()
    def get_serializer_class(self):
        if self.request.method == 'GET':
            return ProductSerializer
        return ProductCreateUpdateSerializer

class ProductDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
    queryset = Product.objects.all()
    def get_serializer_class(self):
        if self.request.method == 'GET':
            return ProductSerializer
        return ProductCreateUpdateSerializer

urlpatterns = [
    path('products/', ProductListCreateView.as_view()),
    path('products/<int:pk>/', ProductDetailUpdateDeleteView.as_view()),
]

# Паттерн 3: filter_backends
class ProductViewSet(viewsets.ModelViewSet):
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['category', 'price']

Задание 1.1 — CategoryViewSet

Разбор: ModelViewSet — самый простой способ реализовать полный CRUD. Достаточно указать queryset и serializer_class — DRF сгенерирует все методы (list, create, retrieve, update, partial_update, destroy).

from rest_framework import viewsets
from .models import Category
from .serializers import CategorySerializer

class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

Задание 1.2 — Маршруты для Category

Разбор: DefaultRouter автоматически создаёт все URL для ViewSet. Вызов router.register(r'categories', CategoryViewSet) создаёт маршруты /categories/ и /categories/{pk}/.

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import CategoryViewSet

router = DefaultRouter()
router.register(r'categories', CategoryViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

Задание 2.1 — SupplierViewSet

Разбор: Аналогично Category — модель Supplier не имеет сложной логики сериализации, поэтому ModelViewSet с единственным сериализатором идеален.

from rest_framework import viewsets
from .models import Supplier
from .serializers import SupplierSerializer

class SupplierViewSet(viewsets.ModelViewSet):
    queryset = Supplier.objects.all()
    serializer_class = SupplierSerializer

Задание 2.2 — Маршруты для Supplier

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import SupplierViewSet

router = DefaultRouter()
router.register(r'suppliers', SupplierViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

Задание 3.1 — ProductListCreateView

Разбор: Для Product нужны два сериализатора: ProductSerializer при GET возвращает вложенные объекты Category и Supplier; ProductCreateUpdateSerializer при POST принимает числовые FK-ключи. Переопределяем get_serializer_class().

from rest_framework.generics import ListCreateAPIView
from .models import Product
from .serializers import ProductSerializer, ProductCreateUpdateSerializer

class ProductListCreateView(ListCreateAPIView):
    queryset = Product.objects.all()

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return ProductSerializer
        return ProductCreateUpdateSerializer

Ключевое: ListCreateAPIView обрабатывает только GET (список) и POST (создание). Для GET/деталь/PUT/DELETE нужен отдельный класс (задание 3.2).

Задание 3.2 — ProductDetailUpdateDeleteView

Разбор: RetrieveUpdateDestroyAPIView обрабатывает GET (деталь), PUT, PATCH и DELETE. Та же логика get_serializer_class() — при GET возвращаем полный объект, при изменении принимаем FK.

from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .models import Product
from .serializers import ProductSerializer, ProductCreateUpdateSerializer

class ProductDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
    queryset = Product.objects.all()

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return ProductSerializer
        return ProductCreateUpdateSerializer

Задание 3.3 — Маршруты для Product

Разбор: При использовании Generic Views (не ViewSet) маршруты регистрируются вручную через path(). Обязательно вызываем .as_view().

from django.urls import path
from .views import ProductListCreateView, ProductDetailUpdateDeleteView

urlpatterns = [
    path('products/', ProductListCreateView.as_view()),
    path('products/<int:pk>/', ProductDetailUpdateDeleteView.as_view()),
]

Задание 4.1 — ProductDetailListCreateView

from rest_framework.generics import ListCreateAPIView
from .models import ProductDetail
from .serializers import ProductDetailSerializer, ProductDetailCreateUpdateSerializer

class ProductDetailListCreateView(ListCreateAPIView):
    queryset = ProductDetail.objects.all()

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return ProductDetailSerializer
        return ProductDetailCreateUpdateSerializer

Задание 4.2 — ProductDetailDetailUpdateDeleteView

from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .models import ProductDetail
from .serializers import ProductDetailSerializer, ProductDetailCreateUpdateSerializer

class ProductDetailDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
    queryset = ProductDetail.objects.all()

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return ProductDetailSerializer
        return ProductDetailCreateUpdateSerializer

Задание 4.3 — Маршруты для ProductDetail

from django.urls import path
from .views import ProductDetailListCreateView, ProductDetailDetailUpdateDeleteView

urlpatterns = [
    path('product-details/', ProductDetailListCreateView.as_view()),
    path('product-details/<int:pk>/', ProductDetailDetailUpdateDeleteView.as_view()),
]

Задание 5.1 — AddressViewSet

from rest_framework import viewsets
from .models import Address
from .serializers import AddressSerializer

class AddressViewSet(viewsets.ModelViewSet):
    queryset = Address.objects.all()
    serializer_class = AddressSerializer

Задание 5.2 — Маршруты для Address

from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import AddressViewSet

router = DefaultRouter()
router.register(r'addresses', AddressViewSet)

urlpatterns = [
    path('', include(router.urls)),
]

Задание 6.1 — CustomerListCreateView

from rest_framework.generics import ListCreateAPIView
from .models import Customer
from .serializers import CustomerSerializer, CustomerCreateUpdateSerializer

class CustomerListCreateView(ListCreateAPIView):
    queryset = Customer.objects.all()

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return CustomerSerializer
        return CustomerCreateUpdateSerializer

Задание 6.2 — CustomerDetailUpdateDeleteView

from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .models import Customer
from .serializers import CustomerSerializer, CustomerCreateUpdateSerializer

class CustomerDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
    queryset = Customer.objects.all()

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return CustomerSerializer
        return CustomerCreateUpdateSerializer

Задание 6.3 — Маршруты для Customer

from django.urls import path
from .views import CustomerListCreateView, CustomerDetailUpdateDeleteView

urlpatterns = [
    path('customers/', CustomerListCreateView.as_view()),
    path('customers/<int:pk>/', CustomerDetailUpdateDeleteView.as_view()),
]

Задание 7.1 — OrderListCreateView

from rest_framework.generics import ListCreateAPIView
from .models import Order
from .serializers import OrderSerializer, OrderCreateUpdateSerializer

class OrderListCreateView(ListCreateAPIView):
    queryset = Order.objects.all()

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return OrderSerializer
        return OrderCreateUpdateSerializer

Задание 7.2 — OrderDetailUpdateDeleteView

from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .models import Order
from .serializers import OrderSerializer, OrderCreateUpdateSerializer

class OrderDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
    queryset = Order.objects.all()

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return OrderSerializer
        return OrderCreateUpdateSerializer

Задание 7.3 — Маршруты для Order

from django.urls import path
from .views import OrderListCreateView, OrderDetailUpdateDeleteView

urlpatterns = [
    path('orders/', OrderListCreateView.as_view()),
    path('orders/<int:pk>/', OrderDetailUpdateDeleteView.as_view()),
]

Задание 8.1 — OrderItemListCreateView

from rest_framework.generics import ListCreateAPIView
from .models import OrderItem
from .serializers import OrderItemSerializer, OrderItemCreateUpdateSerializer

class OrderItemListCreateView(ListCreateAPIView):
    queryset = OrderItem.objects.all()

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return OrderItemSerializer
        return OrderItemCreateUpdateSerializer

Задание 8.2 — OrderItemDetailUpdateDeleteView

from rest_framework.generics import RetrieveUpdateDestroyAPIView
from .models import OrderItem
from .serializers import OrderItemSerializer, OrderItemCreateUpdateSerializer

class OrderItemDetailUpdateDeleteView(RetrieveUpdateDestroyAPIView):
    queryset = OrderItem.objects.all()

    def get_serializer_class(self):
        if self.request.method == 'GET':
            return OrderItemSerializer
        return OrderItemCreateUpdateSerializer

Задание 8.3 — Маршруты для OrderItem

from django.urls import path
from .views import OrderItemListCreateView, OrderItemDetailUpdateDeleteView

urlpatterns = [
    path('order-items/', OrderItemListCreateView.as_view()),
    path('order-items/<int:pk>/', OrderItemDetailUpdateDeleteView.as_view()),
]

Задание 9.1 — filter_backends для Product

Разбор: Два шага: 1) добавить 'django_filters' в INSTALLED_APPS; 2) добавить filter_backends и filterset_fields в ViewSet. Теперь можно фильтровать: GET /api/products/?category=2&price=100

# settings.py
INSTALLED_APPS = [
    # ...
    'django_filters',
]

# views.py
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
from .models import Product
from .serializers import ProductSerializer, ProductCreateUpdateSerializer

class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['category', 'price']

Задание 9.2 — filter_backends для Customer

Разбор: Аналогично Product, но фильтрация по строковым полям first_name и last_name. Работает точное совпадение: ?first_name=Иван.

# views.py
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets
from .models import Customer
from .serializers import CustomerSerializer, CustomerCreateUpdateSerializer

class CustomerViewSet(viewsets.ModelViewSet):
    queryset = Customer.objects.all()
    serializer_class = CustomerSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ['first_name', 'last_name']
Внимание: По умолчанию DjangoFilterBackend использует точное совпадение для строк. Для фильтрации по частичному совпадению (icontains) нужно создать отдельный FilterSet класс с явным указанием lookup_expr='icontains'. ⚠️ Проверить по документации: django-filter FilterSet
← К оглавлению урока    ← Задания    Ошибки →