본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Backend

DRF Serializer와 관계 표현

작성 2026. 10. 12.추가
목차4개 항목

Django REST Framework(DRF)는 Django를 HTML 대신 JSON을 응답하는 API 서버로 만들어 주는 라이브러리다. 그 중심인 Serializer가 모델 인스턴스와 JSON 사이의 변환과 입력 검증을 맡는다. 화면은 Vue나 React 같은 프론트엔드가 그리고 서버는 데이터만 제공하는 구조의 출발점이다.

Serializer가 하는 일

방향사용법용도
모델 → JSONSerializer(instance).data (여러 개면 many=True)조회 응답
JSON → 모델Serializer(data=request.data) → is_valid() → save()생성
수정Serializer(instance, data=request.data)instance와 data를 함께 넘기면 수정

ModelSerializer는 Meta에 모델과 필드만 지정하면 필드 정의와 검증을 자동으로 만든다. Django의 ModelForm과 같은 역할이다(Form과 ModelForm 참고).

관계를 JSON으로: 중첩 Serializer

Movie : Actor = M : N, Movie : Review = 1 : N 구조에서 영화 상세에 배우와 리뷰를 함께 담으려면 관계 필드를 다른 Serializer로 선언한다.

class ActorNameSerializer(serializers.ModelSerializer):
    class Meta:
        model = Actor
        fields = ('name',)
 
class ReviewSimpleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Review
        fields = ('title', 'content')
 
class MovieSerializer(serializers.ModelSerializer):
    actors = ActorNameSerializer(many=True, read_only=True)          # M:N 정참조
    review_set = ReviewSimpleSerializer(many=True, read_only=True)   # 1:N 역참조
 
    class Meta:
        model = Movie
        fields = '__all__'

반대로 리뷰에서 영화를 보여 줄 때는 영화가 하나이므로 many를 쓰지 않는다.

class MovieTitleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Movie
        fields = ('title',)
 
class ReviewSerializer(serializers.ModelSerializer):
    movie = MovieTitleSerializer(read_only=True)   # 리뷰는 영화 하나에 속한다 → many=False
 
    class Meta:
        model = Review
        fields = ('id', 'movie', 'title', 'content')
  • 필드 이름은 정참조·역참조 규칙을 따른다. 정참조는 필드명(actors), 역참조는 모델명_set이나 related_name이다(모델 관계 참고).
  • many=True는 "여러 개"일 때만 쓴다. 단일 객체에 붙이거나 존재하지 않는 역참조 이름을 선언하면 오류가 난다.
  • read_only=True인 필드는 응답에만 나오고 입력 검증에서는 빠진다. 그래서 리뷰를 만들 때 movie는 URL에서 받은 객체를 save(movie=movie)로 직접 넘긴다.
  • 같은 이름의 Serializer를 파일마다 다시 정의하면 import가 꼬인다. MovieListSerializer, MovieTitleSerializer처럼 용도를 드러내는 이름으로 구분한다.

View: @api_view

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
 
@api_view(['POST'])
def review_create(request, movie_pk):
    movie = get_object_or_404(Movie, pk=movie_pk)
    serializer = ReviewSerializer(data=request.data)
    if serializer.is_valid(raise_exception=True):     # 실패하면 400 응답을 자동 반환
        serializer.save(movie=movie)
        return Response(serializer.data, status=status.HTTP_201_CREATED)
 
@api_view(['GET', 'PUT', 'DELETE'])
def review_detail(request, review_pk):
    review = get_object_or_404(Review, pk=review_pk)
 
    if request.method == 'GET':
        return Response(ReviewSerializer(review).data)
 
    if request.method == 'PUT':
        serializer = ReviewSerializer(review, data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data)
 
    review.delete()
    return Response(status=status.HTTP_204_NO_CONTENT)
  • @api_view는 허용 메서드를 명시하고, 템플릿 대신 Response로 JSON을 돌려준다.
  • is_valid(raise_exception=True)를 쓰면 검증 실패 처리를 따로 쓰지 않아도 된다.
  • 상태 코드는 숫자 대신 status.HTTP_201_CREATED처럼 상수를 쓰면 의도가 드러난다. 어떤 상황에 어떤 코드를 쓸지는 REST API 설계의 표를 따른다.
  • 목록 조회는 get_list_or_404 대신 objects.all()로 빈 배열을 돌려준다.

정리

  • Serializer는 모델 ↔ JSON 변환과 검증을 맡고, 관계는 중첩 Serializer로 표현한다.
  • 관계 필드 이름은 DB 관계를 먼저 정리해야 정할 수 있다. 외래키·M:N 설계가 API 응답 구조까지 결정한다.
  • 생성·수정은 is_valid(raise_exception=True) → save(), 응답은 Response와 상태 코드 상수.

관련 노트: REST API 설계, Django 모델 관계 (1:1, 1:N, M:N), Django ORM과 QuerySet, Django Form과 ModelForm

댓글

GitHub 계정으로 댓글을 남길 수 있어요.