← Назад к вопросам

Опиши всю цепочку для создания endpoint по получению сущностей

1.8 Middle🔥 181 комментариев
#REST API и HTTP#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Полная цепочка создания endpoint для получения сущностей

Это фундаментальный вопрос, демонстрирующий понимание архитектуры REST API. Рассмотрим полный процесс на примере списка постов (Posts).

1. Определение модели (models.py)

Начинаем с создания модели данных, которая представляет структуру сущности в базе данных.

from django.db import models

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(
        "auth.User",
        on_delete=models.CASCADE,
        related_name="posts"
    )
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    is_published = models.BooleanField(default=False)
    
    class Meta:
        ordering = ["-created_at"]
        indexes = [models.Index(fields=["-created_at"])]
    
    def __str__(self):
        return self.title

2. Создание сериализатора (serializers.py)

Сериализатор преобразует модель в JSON и обратно, а также валидирует данные.

from rest_framework import serializers
from .models import Post

class AuthorSerializer(serializers.Serializer):
    """Сокращенная информация об авторе"""
    id = serializers.IntegerField()
    username = serializers.CharField()

class PostSerializer(serializers.ModelSerializer):
    author = AuthorSerializer(read_only=True)
    author_id = serializers.IntegerField(write_only=True)
    
    class Meta:
        model = Post
        fields = [
            "id",
            "title",
            "content",
            "author",
            "author_id",
            "created_at",
            "updated_at",
            "is_published"
        ]
        read_only_fields = ["id", "created_at", "updated_at"]
    
    def validate_title(self, value):
        if len(value) < 3:
            raise serializers.ValidationError(
                "Заголовок должен содержать минимум 3 символа"
            )
        return value

3. Создание ViewSet или View (views.py)

Представление обрабатывает HTTP запросы и возвращает HTTP ответы. Рассмотрим оба подхода.

Вариант 1: ViewSet (рекомендуется)

from rest_framework import viewsets
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 rest_framework.filters import SearchFilter, OrderingFilter

from .models import Post
from .serializers import PostSerializer

class PostViewSet(viewsets.ModelViewSet):
    """
    CRUD операции для постов.
    GET /api/v1/posts/ - список всех постов
    POST /api/v1/posts/ - создание нового поста
    GET /api/v1/posts/{id}/ - получение конкретного поста
    PUT /api/v1/posts/{id}/ - полное обновление поста
    PATCH /api/v1/posts/{id}/ - частичное обновление
    DELETE /api/v1/posts/{id}/ - удаление поста
    """
    
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    filterset_fields = ["author", "is_published"]
    search_fields = ["title", "content"]
    ordering_fields = ["created_at", "title"]
    ordering = ["-created_at"]
    
    def get_queryset(self):
        """Оптимизируем запросы с select_related и prefetch_related"""
        queryset = super().get_queryset()
        queryset = queryset.select_related("author")
        
        # Если не опубликовано, могут видеть только авторы
        if not self.request.user.is_staff:
            queryset = queryset.filter(
                models.Q(is_published=True) |
                models.Q(author=self.request.user)
            )
        
        return queryset
    
    def perform_create(self, serializer):
        """Автоматически устанавливаем автора"""
        serializer.save(author=self.request.user)
    
    @action(detail=True, methods=["post"])
    def publish(self, request, pk=None):
        """Кастомный экшн для публикации поста"""
        post = self.get_object()
        post.is_published = True
        post.save()
        return Response(
            {"status": "пост опубликован"},
            status=status.HTTP_200_OK
        )

Вариант 2: Простой APIView для GET

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

class PostListView(APIView):
    def get(self, request):
        """Получить список всех опубликованных постов"""
        posts = Post.objects.filter(is_published=True)
        serializer = PostSerializer(posts, many=True)
        return Response(serializer.data, status=status.HTTP_200_OK)
    
    def post(self, request):
        """Создать новый пост"""
        serializer = PostSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save(author=request.user)
            return Response(
                serializer.data,
                status=status.HTTP_201_CREATED
            )
        return Response(
            serializer.errors,
            status=status.HTTP_400_BAD_REQUEST
        )

4. Регистрация в маршрутизаторе (urls.py)

При использовании ViewSet:

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

# Маршрутизатор автоматически генерирует все URL patterns
router = DefaultRouter()
router.register(r"posts", PostViewSet, basename="post")

urlpatterns = [
    path("api/v1/", include(router.urls)),
]

# Генерируемые endpoints:
# GET    /api/v1/posts/                    - список
# POST   /api/v1/posts/                    - создание
# GET    /api/v1/posts/{id}/               - деталь
# PUT    /api/v1/posts/{id}/               - обновление
# PATCH  /api/v1/posts/{id}/               - частичное обновление
# DELETE /api/v1/posts/{id}/               - удаление
# POST   /api/v1/posts/{id}/publish/       - кастомный экшн

При использовании APIView:

from django.urls import path
from .views import PostListView, PostDetailView

urlpatterns = [
    path("api/v1/posts/", PostListView.as_view(), name="post-list"),
    path("api/v1/posts/<int:pk>/", PostDetailView.as_view(), name="post-detail"),
]

5. Добавление в главный urls.py проекта

# myproject/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("posts.urls")),  # Включаем URLs приложения
]

6. Настройка REST_FRAMEWORK (settings.py)

REST_FRAMEWORK = {
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 20,
    "DEFAULT_FILTER_BACKENDS": [
        "django_filters.rest_framework.DjangoFilterBackend",
        "rest_framework.filters.SearchFilter",
        "rest_framework.filters.OrderingFilter",
    ],
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.TokenAuthentication",
        "rest_framework.authentication.SessionAuthentication",
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticatedOrReadOnly",
    ],
    "DEFAULT_THROTTLE_CLASSES": [
        "rest_framework.throttling.AnonRateThrottle",
        "rest_framework.throttling.UserRateThrottle"
    ],
    "DEFAULT_THROTTLE_RATES": {
        "anon": "100/hour",
        "user": "1000/hour"
    },
}

7. Тестирование endpoint (tests.py)

from rest_framework.test import APITestCase
from rest_framework import status
from django.contrib.auth.models import User
from .models import Post

class PostAPITestCase(APITestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username="testuser",
            password="testpass"
        )
        self.post = Post.objects.create(
            title="Test Post",
            content="Test content",
            author=self.user,
            is_published=True
        )
    
    def test_get_posts_list(self):
        """Тест получения списка постов"""
        response = self.client.get("/api/v1/posts/")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 1)
        self.assertEqual(response.data[0]["title"], "Test Post")
    
    def test_create_post(self):
        """Тест создания поста"""
        self.client.force_authenticate(user=self.user)
        data = {
            "title": "New Post",
            "content": "New content",
            "author_id": self.user.id
        }
        response = self.client.post("/api/v1/posts/", data)
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(Post.objects.count(), 2)

Полная цепочка в двух словах

  1. Model → структура данных в БД
  2. Serializer → преобразование Model ↔ JSON
  3. ViewSet/View → бизнес-логика и обработка запросов
  4. Router/URLs → маршрутизация HTTP запросов
  5. Settings → конфигурация DRF
  6. Tests → проверка корректности работы

Эта архитектура обеспечивает чистоту кода, разделение ответственности и легкость тестирования.