본문으로 건너뛰기
김신건의 로그

[Django] DRF 개요: Serializer, ViewSet, Router

· 수정 · 📖 약 1분 · 436자/단어 #django #drf #api #rest
django rest framework, DRF, APIView, ViewSet, Serializer, Router

학습 로드맵

이 페이지는 개요. 처음 접하는 사람은 아래 순서로 읽으면 자연스럽다:

flowchart TD
    S["1. [[drf-tutorial-quickstart]]<br/>5분 안에 동작하는 API"]
    S --> RR["2. [[drf-request-response]]<br/>request.data / Response()"]
    RR --> Ser["3. [[django-drf-serializers]]<br/>Model ↔ JSON 변환"]
    Ser --> V["4. [[drf-views]]<br/>APIView, Generic Views"]
    V --> VS["5. [[django-drf-viewset]]<br/>ViewSet + @action"]
    VS --> R["6. [[drf-routers]]<br/>URL 자동 생성"]
    R --> A["7. [[drf-authentication]]<br/>Session, Token, JWT"]
    A --> P["8. [[django-drf-permissions]]<br/>권한 검사"]
    P --> PT["9. [[drf-pagination-throttling]]<br/>페이징 + rate limit"]
    PT --> F["10. [[drf-filtering]]<br/>?q=&search=&ordering="]
    F --> Ver["11. [[drf-versioning-content-negotiation]]<br/>버전 관리 + Accept"]
    Ver --> Doc["12. [[drf-schemas-openapi]]<br/>Swagger 자동 문서화"]
    Doc --> T["13. [[drf-testing]]<br/>APIClient 테스트"]

DRF 는 파이프라인이 핵심. 한번 그림으로 보면 이해가 쉽다:

정의

**Django REST Framework (DRF)**는 Django 위에 REST API 구축을 위한 사실상 표준 라이브러리. Serializer(데이터 변환·검증), View/ViewSet(엔드포인트), Router(URL 자동 생성), Permission/Throttle/Filter 시스템을 제공한다. OpenAPI 자동 생성도 가능.

설치

pip install djangorestframework
INSTALLED_APPS = [
    ...,
    "rest_framework",
]

Serializer

# blog/serializers.py
from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    author = serializers.StringRelatedField(read_only=True)
    tag_count = serializers.IntegerField(source="tags.count", read_only=True)

    class Meta:
        model = Post
        fields = ["id", "title", "body", "author", "tag_count", "created_at"]
        read_only_fields = ["created_at"]

직렬화:

post = Post.objects.first()
serializer = PostSerializer(post)
serializer.data       # {"id": 1, "title": "...", ...}

역직렬화 + 검증 + 저장:

serializer = PostSerializer(data=request.data)
if serializer.is_valid():
    post = serializer.save(author=request.user)
else:
    print(serializer.errors)

일반 Serializer (Form 비슷)

class CommentSerializer(serializers.Serializer):
    post_id = serializers.IntegerField()
    body = serializers.CharField(max_length=500)

    def validate_body(self, value):
        if "스팸" in value:
            raise serializers.ValidationError("부적절한 내용")
        return value

    def validate(self, data):
        if not Post.objects.filter(id=data["post_id"]).exists():
            raise serializers.ValidationError("존재하지 않는 글")
        return data

    def create(self, validated_data):
        return Comment.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.body = validated_data["body"]
        instance.save()
        return instance

ModelSerializer가 대부분 자동화하지만 일반 Serializer는 더 자유로움.

APIView (function-based 권장 안 함)

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

class PostListAPI(APIView):
    def get(self, request):
        posts = Post.objects.all()
        serializer = PostSerializer(posts, many=True)
        return Response(serializer.data)

    def post(self, request):
        serializer = PostSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        post = serializer.save(author=request.user)
        return Response(PostSerializer(post).data, status=status.HTTP_201_CREATED)

generic views

from rest_framework import generics

class PostListCreate(generics.ListCreateAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer

class PostDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    lookup_field = "slug"

자주 쓰는 패턴 즉시 사용.

ViewSet

from rest_framework import viewsets

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.select_related("author")
    serializer_class = PostSerializer

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

    def get_queryset(self):
        qs = super().get_queryset()
        if q := self.request.query_params.get("q"):
            qs = qs.filter(title__icontains=q)
        return qs

CRUD 모두 한 클래스에. ModelViewSet은 list, retrieve, create, update, partial_update, destroy 액션 자동.

Router

# urls.py
from rest_framework.routers import DefaultRouter
from .views import PostViewSet

router = DefaultRouter()
router.register("posts", PostViewSet, basename="post")

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

자동 생성된 URL:

GET    /api/posts/         → list
POST   /api/posts/         → create
GET    /api/posts/{pk}/    → retrieve
PUT    /api/posts/{pk}/    → update
PATCH  /api/posts/{pk}/    → partial_update
DELETE /api/posts/{pk}/    → destroy

@action: 커스텀 액션

from rest_framework.decorators import action

class PostViewSet(ModelViewSet):
    @action(detail=True, methods=["post"])
    def publish(self, request, pk=None):
        post = self.get_object()
        post.is_published = True
        post.save()
        return Response({"status": "published"})

    @action(detail=False, methods=["get"])
    def featured(self, request):
        qs = self.get_queryset().filter(is_featured=True)
        page = self.paginate_queryset(qs)
        serializer = self.get_serializer(page, many=True)
        return self.get_paginated_response(serializer.data)

URL: POST /api/posts/{pk}/publish/, GET /api/posts/featured/.

Permission

# settings.py
REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticated",
    ],
}

# 또는 view 별
class PostViewSet(ModelViewSet):
    permission_classes = [IsAuthenticatedOrReadOnly]

자주 쓰는 것:

  • AllowAny
  • IsAuthenticated
  • IsAdminUser
  • IsAuthenticatedOrReadOnly
  • 커스텀

커스텀 권한

from rest_framework import permissions

class IsAuthorOrReadOnly(permissions.BasePermission):
    def has_permission(self, request, view):
        return request.method in permissions.SAFE_METHODS or request.user.is_authenticated

    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        return obj.author == request.user

Authentication

# settings.py
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.SessionAuthentication",
        "rest_framework.authentication.TokenAuthentication",
    ],
}
  • SessionAuthentication: Django 세션 (브라우저)
  • BasicAuthentication: HTTP Basic (디버그용)
  • TokenAuthentication: DB에 저장된 토큰
  • JWTAuthentication (simplejwt): stateless JWT

JWT 설정

pip install djangorestframework-simplejwt
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework_simplejwt.authentication.JWTAuthentication",
    ],
}

# urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView

urlpatterns = [
    path("api/token/", TokenObtainPairView.as_view()),
    path("api/token/refresh/", TokenRefreshView.as_view()),
]

Pagination

REST_FRAMEWORK = {
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 20,
}

자동 적용. 응답:

{
    "count": 100,
    "next": "https://.../api/posts/?page=3",
    "previous": "https://.../api/posts/?page=1",
    "results": [...]
}

다른 페이지네이션:

  • LimitOffsetPagination: ?limit=10&offset=20
  • CursorPagination: 안정적 큰 데이터셋

Filter / Search / Ordering

pip install django-filter
REST_FRAMEWORK = {
    "DEFAULT_FILTER_BACKENDS": [
        "django_filters.rest_framework.DjangoFilterBackend",
        "rest_framework.filters.SearchFilter",
        "rest_framework.filters.OrderingFilter",
    ],
}

class PostViewSet(ModelViewSet):
    filterset_fields = ["author", "is_published"]
    search_fields = ["title", "body"]
    ordering_fields = ["created_at", "views"]

쿼리:

GET /api/posts/?author=1&is_published=true
GET /api/posts/?search=django
GET /api/posts/?ordering=-views

Throttle

REST_FRAMEWORK = {
    "DEFAULT_THROTTLE_CLASSES": [
        "rest_framework.throttling.AnonRateThrottle",
        "rest_framework.throttling.UserRateThrottle",
    ],
    "DEFAULT_THROTTLE_RATES": {
        "anon": "100/day",
        "user": "1000/day",
    },
}

레이트 리밋. Redis 백엔드 권장 (django-throttle).

OpenAPI / Swagger

pip install drf-spectacular
INSTALLED_APPS += ["drf_spectacular"]
REST_FRAMEWORK["DEFAULT_SCHEMA_CLASS"] = "drf_spectacular.openapi.AutoSchema"

# 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"),
]

자동으로 OpenAPI 3 스펙 + Swagger UI 생성.

함정

1. ModelSerializer로 노출 위험

class UserSerializer(ModelSerializer):
    class Meta:
        model = User
        fields = "__all__"    # password 해시 노출!

fields/exclude 명시. password, 내부 필드 제외.

2. N+1 in serializer

class PostSerializer(ModelSerializer):
    author_name = serializers.CharField(source="author.username")
    # author 매번 조회 → N+1

# ViewSet에서
class PostViewSet(ModelViewSet):
    queryset = Post.objects.select_related("author")    # 미리 JOIN

3. PUT vs PATCH

  • PUT: 전체 교체 (모든 필드 필요)
  • PATCH: 부분 업데이트 (partial=True)
serializer = PostSerializer(post, data=request.data, partial=True)

ModelViewSetpartial_update 액션이 자동 처리.

4. validate_<field> vs validate

  • validate_<field>: 단일 필드
  • validate: 다중 필드 관계 (cleaned data 전체)

5. Write/Read 분리

class PostSerializer(ModelSerializer):
    tags = serializers.PrimaryKeyRelatedField(many=True, queryset=Tag.objects.all())
    tag_names = TagSerializer(many=True, source="tags", read_only=True)

    class Meta:
        model = Post
        fields = ["id", "title", "tags", "tag_names"]

write는 ID로 받고 read는 nested로.

이 글의 용어 (13개)
[Django] DRF Permission, Throttle, Filterdjango
정의 DRF는 view 호출 전 인증(Authentication) → 권한(Permission) → 스로틀(Throttle) 순으로 검사. 각 단계가 통과해야 view 메서드 실…
[Django] DRF Serializer 심화django
정의 DRF Serializer는 JSON ↔ Python ↔ Model의 변환과 검증을 담당한다. Django Form과 비슷한 인터페이스이나 API/JSON에 특화. 검증, …
[Django] DRF ViewSet, Router, @actiondjango
정의 ViewSet은 같은 리소스의 관련 view들을 클래스 하나로 묶는다. Django의 generic views와 비슷하나 / / / / / action 메서드로 통일. Ro…
[DRF] Authentication: Session, Token, JWT, OAuth2django
정의 Authentication = 누가 요청했는지 식별. Authorization () 과 별개. 흐름 자세한 permission 은 . 내장 Authentication Cla…
[DRF] Filtering: django-filter, SearchFilter, OrderingFilterdjango
정의 Filtering = 쿼리 파라미터로 결과 제한. 3가지 backend + custom. 3가지 내장 Backend 설치 + 설정 1. DjangoFilterBackend …
[DRF] Pagination + Throttlingdjango
정의 Pagination = 큰 목록을 페이지 단위로 나눔. Throttling = 요청 빈도 제한. 둘 다 API 안정성의 기본. Pagination 3종 언제 어느 것을 쓸까…
[DRF] Request + Response 객체django
정의 DRF 의 = Django 의 확장. = 의 확장. 자동 content negotiation + renderer 처리. 요청이 뷰에 도달하기까지 에 값이 실제로 들어가려면 …
[DRF] Routers: DefaultRouter, SimpleRouter, custom actionsdjango
정의 Router = ViewSet 에 대한 URL 자동 생성. Rails 의 와 유사. 왜 Router? 종류 DefaultRouter 생성되는 URL: | URL | Meth…
[DRF] Schemas + OpenAPI: 자동 문서화django
정의 OpenAPI (전 Swagger) = REST API를 기계 가독 스펙으로 기술하는 표준. DRF는 이 스펙을 ViewSet / Serializer 로부터 자동 추출할 수…
[DRF] Testing: APIClient, APITestCase, force_authenticatedjango
정의 DRF의 테스트 도구는 Django 위에 API 특화 helper 를 얹은 것. 로 real endpoint 를 호출하거나 로 View 단위 테스트가 가능. 테스트 도구 계…
[DRF] Tutorial + Quickstart: 첫 API 서버 5분django
정의 Django REST Framework (DRF) 를 처음 사용 하는 사람을 위한 순차 시작 가이드. 5분 안에 동작하는 API 서버 완성. DRF 개요는 . Django …
[DRF] Versioning + Content Negotiationdjango
정의 Versioning = API 의 여러 버전 공존. Content Negotiation = 같은 endpoint 가 여러 형식 (JSON, XML, HTML) 응답. Ver…
[DRF] Views: APIView, GenericViews, Mixins, FBVdjango
정의 DRF View 4가지 스타일. 자유도 vs 자동화 트레이드오프. 4가지 스타일 | 스타일 | 코드량 | 자유도 | |---|---|---| | | 유사 Django FBV…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기