Django - DRF

김기훈·2025년 11월 20일

Django

목록 보기
3/17
post-thumbnail

DRF (Django REST Framwork)


  • DRF란?

    • Django에서 웹 API를 쉽게 구축할 수 있도록 도와주는 강력한 라이브러리
      • 직렬화(Serialization), 뷰(View), 권한 부여(Authorization) 등의 기능을 제공
      • RESTful API 개발을 단순화함
  • 핵심 기능

    • Serializer → 데이터 구조화/검증
    • View / ViewSet → 로직 처리
    • Router → URL 자동 매핑

DRF의 주요 개념들 ⭐️

1. Router

  • URL과 ViewSet을 자동으로 연결해주는 기능
    • 이를 통해 개발자는 URLConf를 일일이 작성할 필요 없이, ViewSet만으로 URL 매핑이 가능
from rest_framework.routers import DefaultRouter
from .views import MyModelViewSet

router = DefaultRouter()
router.register(r'mymodels', MyModelViewSet)

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

2. Permissions

  • API에 대한 접근 권한을 제어하는 기능
    • 기본적으로 DRF는 다양한 권한 클래스를 제공하며, 이를 통해 API 접근을 제한 가능
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView

class MyAPIView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request):
        return Response({"message": "Authenticated!"})
  • IsAuthenticated: 인증된 사용자만 접근 가능
  • IsAdminUser: 관리자만 접근 가능
  • AllowAny: 누구나 접근 가능

3. Authentication

  • DRF는 다양한 인증 방식을 지원
  • 기본적인 Session 인증, Token 인증 외에도 OAuth, JWT 등의 다양한 인증 방식을 사용 가능
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView

class MyAPIView(APIView):
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def get(self, request):
        return Response({"message": "Token authenticated!"})

4. Throttling

  • API 요청을 제한하는 기능
  • 특정 시간 내에 요청할 수 있는 횟수를 제한하여 API 남용을 방지 가능
from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView

class MyAPIView(APIView):
    throttle_classes = [UserRateThrottle]

    def get(self, request):
        return Response({"message": "Throttled request!"})

Serializer ⭐️

  • 정의

    • 데이터를 JSON으로 변환하고, 입력 데이터를 검증하는 Form + ModelForm의 DRF 버전
    • Form의 검증 역할 + JSON 변환까지 처리하는 핵심 클래스
  • 기능

    • 모델 인스턴스 <-> JSON 데이터 = 데이터의 직렬화/역직렬화 담당
      • Django 모델 인스턴스를 JSON과 같은 데이터 포맷으로 변환
      • JSON 데이터를 모델 인스턴스로 변환하는 데 사용
    • 필드 validation
    • create/update 로직
    • 중첩 구조(Nested) 까지 지원
    • Serializer를 두개 사용도 가능
  • 필요한 이유

    • Template 기반이 아닌 외부 클라이언트(React, 모바일 앱, Flutter 등)에 JSON 응답을 위해 필요

Serializer 두 개 이상 사용

상황에 따라 다른 Serializer 사용

  • ex
    • GET 요청 → 목록 또는 상세 조회용 Serializer
    • POST / PUT / PATCH 요청 → 생성·수정 전용 Serializer
# ViewSet 하나에서 3개의 Serializer를 사용

class UserViewSet(ModelViewSet):
    queryset = User.objects.all()

    def get_serializer_class(self):
        if self.action == 'list':
            return UserListSerializer
        if self.action == 'retrieve':
            return UserDetailSerializer
        return UserCreateUpdateSerializer

Serializer를 중첩 사용

# Serializer 두 개를 동시에 쓰는 구조.

class ProfileSerializer(serializers.ModelSerializer):
    class Meta:
        model = Profile
        fields = ['age', 'bio']


class UserSerializer(serializers.ModelSerializer):
    profile = ProfileSerializer()  # 중첩된 Serializer 사용

    class Meta:
        model = User
        fields = ['username', 'email', 'profile']

Response / Request serializer 따로 두기

  • ex
    • Response 용 Serializer → nested 관계, read_only 구조
    • Request 용 Serializer → 단순 필드 + validation 전용
class UserCreateSerializer(serializers.Serializer):
    username = serializers.CharField()
    password = serializers.CharField()


class UserResponseSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username']

SerializerMethodField

  • 를 이용하여 다른 serializer 호출하기

# Serializer 두 개 사용

class PostSerializer(serializers.ModelSerializer):
    comments = serializers.SerializerMethodField()

    def get_comments(self, obj):
        return CommentSerializer(obj.comments.all(), many=True).data

사용법

  from rest_framework import serializers
  from .models import MyModel
                               ⎷
  class MyModelSerializer(serializers.ModelSerializer):
      class Meta:            
          model = MyModel
          fields = '__all__'
  • Serializer: 더 복잡한 로직이 필요할 때 사용되며, 필드와 그 처리 로직을 커스터마이징 가능
    • ModelSerializer: Django 모델을 기반으로 하는 직렬화를 자동으로 처리

사용 예시(기초)

# model
class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    
# Serializer
class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['id', 'title', 'content']

# serialize된 결과(JSON)
{
  "id": 1,
  "title": "Hello",
  "content": "World"
}

사용 예시(검증)

# 검증(Validation)도 가능
class PostSerializer(serializers.ModelSerializer):
    title = serializers.CharField(max_length=10)

    class Meta:
        model = Post
        fields = "__all__"
        
## 10자 넘으면 에러:
{
  "title": ["Ensure this field has no more than 10 characters."]
}

주요 메서드

validate(self, attrs)

  • 필드 간 검증이 필요할 때 사용

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['title', 'content', 'is_published']

    def validate(self, attrs): ✅
        title = attrs.get('title')
        content = attrs.get('content')

        if title == content:
            raise serializers.ValidationError("제목과 내용은 같을 수 없습니다.") 

        return attrs
        
# 호출 흐름
serializer = PostSerializer(data={
    "title": "Hello",
    "content": "Hello"
})

serializer.is_valid()  
# => {"non_field_errors": ["제목과 내용은 같을 수 없습니다."]}

to_representation(self, instance)

  • 출력(JSON 변환)을 커스터마이징하고 싶을 때 사용

# 기본 예제 모델
class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    is_published = models.BooleanField(default=False)
    
# serializer    
class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['id', 'title', 'content', 'is_published']

    def to_representation(self, instance):
        rep = super().to_representation(instance)# 커스텀 출력
        rep['title_length'] = len(instance.title)  # 필드 추가
        rep['status'] = '공개' if instance.is_published else '비공개'

        return rep

# 출력
      "id": 1,
      "title": "Hello",
      "content": "World",
      "is_published": true,
      "title_length": 5,
      "status": "공개"

create(self, validated_data)

  • 요청으로 새로운 객체를 만들 때 호출됨(새로운 모델 인스턴스를 생성할 때 사용)

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['title', 'content', 'is_published']

    def create(self, validated_data):
        print("create 호출됨:", validated_data)
        return Post.objects.create(**validated_data)# 호출 흐름
serializer = PostSerializer(data={
    "title": "New",
    "content": "Hello",
    "is_published": True
})

serializer.is_valid(raise_exception=True)
post = serializer.save()     # <===== create() 실행됨

# 출력
create 호출됨: {'title': 'New', 'content': 'Hello', 'is_published': True}

update(self, instance, validated_data)

  • PUT/PATCH 요청에서 기존 객체를 수정할 때 호출됨
class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ['title', 'content', 'is_published']

    def update(self, instance, validated_data):print("update 호출됨:", validated_data)

        instance.title = validated_data.get('title', instance.title)
        instance.content = validated_data.get('content', instance.content)
        instance.is_published = validated_data.get('is_published', instance.is_published)
        instance.save()
        return instance

# 호출 흐름
serializer = PostSerializer(post_instance, data={
    "title": "Updated Title"
}, partial=True)

serializer.is_valid(raise_exception=True)
post = serializer.save()    # <===== update() 실행됨

실제 사용되는 패턴

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = "__all__"

    # 입력 검증
    def validate(self, attrs):
        if len(attrs.get('title', '')) < 3:
            raise serializers.ValidationError("제목은 3글자 이상이어야 합니다.")
        return attrs

    # 생성
    def create(self, validated_data):
        validated_data['title'] = validated_data['title'].title()  
        # 예: 제목을 자동 capitalizing
        return Post.objects.create(**validated_data)

    # 수정
    def update(self, instance, validated_data):
        instance.content = validated_data.get('content', instance.content)
        instance.save()
        return instance

    # 출력 변환
    def to_representation(self, instance):
        rep = super().to_representation(instance)
        rep['summary'] = instance.content[:20]  # 요약 필드 추가
        return rep

View ⭐️

  • 한 줄 정의
    • HTTP 요청(GET/POST/…)을 받고 Serializer를 사용해 응답을 만드는 부분
  • 발전 단계
    • APIView
      • get/post 직접 작성
    • GenericAPIView
      • queryset/serializer_class 지원
    • Mixins
      • list, create 같은 기능 제공
    • ViewSet
      • 여러 HTTP 기능을 한 클래스에 묶음
    • ModelViewSet
      • CRUD 자동화 + serializer + queryset만 넣으면 됨

APIView

  • APIView 방식 (원시 형태) -> 모든 로직을 직접 작성해야 함
    • Django의 기본 View를 확장하여 RESTful API 엔드포인트를 생성할 수 있도록 해줌
    • HTTP 메서드(GET, POST, PUT, DELETE 등)를 오버라이드하여 사용할 수 있음
# 예시 1

class PostListAPIView(APIView):
    def get(self, request):
        posts = Post.objects.all()
        serializer = PostSerializer(posts, many=True)
        return Response(serializer.data)
# 예시 2
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

class MyAPIView(APIView):
    def get(self, request):
        data = {"message": "Hello, World!"}
        return Response(data, status=status.HTTP_200_OK)

    def post(self, request):
        data = request.data
        return Response(data, status=status.HTTP_201_CREATED)

ViewSets


ViewSet

  • 정의

    • ViewSet 은 Django REST Framework에서 제공하는 강력한 기능
    • 여러 CRUD(Create, Retrieve, Update, Delete) 작업을 단일 클래스에서 처리할 수 있게 해줌
  • 기능

    • ModelViewSet, ReadOnlyModelViewSet 등등 이용시 더욱 간결하게 API를 정의 가능

ViewSet의 종류 및 사용법

  • ViewSet: 모든 CRUD 작업을 직접 정의할 수 있는 기본 ViewSet.
  • ModelViewSet: ViewSet을 상속받아 모델의 CRUD 작업을 자동으로 처리
  • ReadOnlyModelViewSet: 읽기 전용 작업(리스트, 상세 조회)만 처리

Custom ViewSet

  • 기본 제공되지 않는 동작이나 커스텀 동작을 추가하고 싶은 경우
    • ViewSet을 직접 상속받아 필요한 메서드를 정의 가능
from rest_framework import viewsets
from rest_framework.response import Response

class CustomViewSet(viewsets.ViewSet):
    def list(self, request):
        data = {"message": "This is a list view"}
        return Response(data)

    def retrieve(self, request, pk=None):
        data = {"message": f"This is the detail view of {pk}"}
        return Response(data)

ModelViewSet

  • ModelViewSet은 가장 많이 사용되는 ViewSet, 모델의 모든 CRUD 작업을 처리
    • querysetserializer_class만 정의하면, 나머지 작업은 자동으로 처리됨

기본 구조 및 예시

class PostViewSet(ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
  • 위의 코드로 아래 기능 전부 자동 생성
    • GET /posts/ | POST /posts/
    • GET /posts/1/ | PUT /posts/1/ | DELETE /posts/1/
from rest_framework import viewsets
from .models import MyModel
from .serializers import MyModelSerializer

class MyModelViewSet(viewsets.ModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer

ReadOnlyModelViewSet

  • ReadOnlyModelViewSet은 읽기 전용 API를 제공하며, listretrieve 작업만 지원
from rest_framework import viewsets
from .models import MyModel
from .serializers import MyModelSerializer

class MyModelReadOnlyViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer

실습

  • BlogViewSet

  • django-extionsion | ipython 설치후 shell_plus 사용 가능

    • INSTALLED_APPS에 django-extionsion 등록 필요

Router


Router ⭐️

  • Router는 URL 라우팅을 자동으로 처리해주는 Django REST Framework의 기능

    • ViewSet과 함께 사용하면, URL 패턴을 자동으로 생성할 수 있어 URL 설정을 간편하게 가능
    • ViewSet을 URL로 자동 연결해주는 매핑 도구
      • ViewSet은 URL을 직접 작성 불가능 -> Router 필요
  • 전체 흐름

    [Client] 
       ↓ HTTP 요청 (GET/POST/PUT/DELETE)
    [Router]
       ↓ URL → 어떤 ViewSet 메서드를 호출할지 결정
    [ViewSet]
       ↓
    Serializer (JSON ↔ Model)
       ↓
    Model (DB Access)
    

Router와 기존 URL 패턴 통합

  • Router를 사용하면서도, 기존의 URL 패턴과 함께 사용 가능
    • 아래의 코드처럼 진행되면 ViewSet으로 관리되는 URL과 개별 뷰 함수가 처리하는 URL을 모두 관리 가능
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import MyModelViewSet, custom_view

router = DefaultRouter()
router.register(r'mymodels', MyModelViewSet)

urlpatterns = [
    path('', include(router.urls)),
    path('custom/', custom_view),

SimpleRouter

  • SimpleRouter는 가장 기본적인 Router로, ViewSet과 연결하여 기본적인 CRUD URL을 자동으로 생성
from rest_framework.routers import SimpleRouter
from .views import MyModelViewSet

router = SimpleRouter()
router.register(r'mymodels', MyModelViewSet)

urlpatterns = router.urls
  • 다음과 같은 URL 패턴이 자동으로 생성
    • GET /mymodels/list
    • GET /mymodels/{pk}/retrieve
    • POST /mymodels/create
    • PUT /mymodels/{pk}/update
    • DELETE /mymodels/{pk}/destroy

DefaultRouter

  • DefaultRouterSimpleRouter의 기능에 더해, 기본 API 루트 엔드포인트를 추가로 생성 해줌
rom rest_framework.routers import DefaultRouter
from .views import MyModelViewSet

router = DefaultRouter()
router.register(r'mymodels', MyModelViewSet)

urlpatterns = router.urls

# urls.py

router = DefaultRouter()
router.register('posts', PostViewSet)

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

# Router이 자동으로 아래와 같은 URL을 만들어 줌
 /posts/   = GET    -> 전체 조회 
 /posts/   = POST   -> 생성    
 /posts/1/ = GET    -> 상세 조회 
 /posts/1/ = PUT    -> 전체 수정 
 /posts/1/ = PATCH  -> 부분 수정 
 /posts/1/ = DELETE -> 삭제    

실습

# model.py
class Post(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    
# serializers.py
class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = "__all__"

# views.py
class PostViewSet(ModelViewSet):
    queryset = Post.objects.all()
    serializer_class = PostSerializer
    
# urls.py
router = DefaultRouter()
router.register("posts", PostViewSet)

urlpatterns = [
    path('', include(router.urls)),
]
  • 이 4개 파일로 CRUD API 완성 (views / urls 를 직접 작성할 필요 X)
    • GET /posts/
    • POST /posts/
    • GET /posts/1/
    • PUT /posts/1/
    • DELETE /posts/1/

DRF 실습

준비

  • poetry add djangorestframework
    • INSTALLED_APPS'rest_framework', 등록
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 10,
}  

  • router.register(r'users', api_views.UserViewSet, basename='user')
    • path('', include(router.urls)) 이걸로 /api/ 는 고정
    • r'users' = include 에서 users로 시작하라는 것과 같은 의미 -> /api/users/

  • /blog / GET -> List | POST -> Create
  • /blog/1 GET -> Detail / PUT, PATCH -> update / DELETE -> delete

profile
안녕하세요.

0개의 댓글