DRF - Serializer

김기훈·2026년 3월 27일

이론

목록 보기
9/10
post-thumbnail

Serializer 📌

Serializer

  • serializers.Serializer

    • 기본 시리얼라이저 클래스
    • 모델과 직접적인 연결이 없으며, 어떤 필드를 직렬화/역직렬화할지 모든 필드를 개발자가 직접 명시해야 함
    • 회원가입 시 이메일 인증이나 로그인처럼 특정 데이터베이스 모델의 CRUD와 직접 매핑되지 않는 기능에 주로 사용

ModelSerializer

  • serializers.ModelSerializer

    • Django의 모델(Model)과 강력하게 결합된 시리얼라이저
      • Meta 클래스에 모델을 지정하면, 모델에 정의된 필드를 기반으로 시리얼라이저 필드를 자동으로 생성해 줌
      • 또한 데이터를 생성하고 수정하는 create()update() 메서드가 기본적으로 구현되어 있음
        • 그리하여, 코드의 양을 획기적으로 줄여줌

Fields

종류

  • EmailField

    • 텍스트 입력값이 유효한 이메일 형식(예: test@test.com)인지 자동으로 검증하는 필드
  • CharField

    • 텍스트(문자열) 데이터를 처리하는 필드
    • 주로 비밀번호, 닉네임, 인증 코드 등에 사용되며 길이를 제한할 수 있음
  • IntegerField

    • 정수형 데이터를 처리
  • DictField

    • 파이썬의 딕셔너리(JSON 객체) 형태의 데이터를 검증하고 처리
  • SerializerMethodField

    • 개념
      • 데이터베이스(Model)에 실제로 존재하지 않는 필드를 시리얼라이저에서 동적으로 계산하거나 조합하여
      • 응답 데이터에 덧붙여주는 읽기 전용(Read-only) 필드
    • 작동 방식
      • 이 필드를 선언하면, DRF는 자동으로 시리얼라이저 내의 get_<field_name>이라는 이름의
      • 메서드를 찾아 실행하고, 그 반환값을 해당 필드의 데이터로 사용

예시

  • SerializerMethodField

    • 예시 설명
      • membership_duration이나 is_newbie는 모델에 저장할 필요가 없는 파생 데이터
      • 클라이언트(프론트엔드)가 직접 계산하게 놔두지 않고
        • 서버 측에서 SerializerMethodField를 통해 가공된 완제품을 내려주면
        • 프론트엔드의 로직이 훨씬 가벼워짐
from rest_framework import serializers
from django.utils import timezone
from .models import User

class UserProfileSerializer(serializers.ModelSerializer):
    # 1. SerializerMethodField 선언
    # DB 모델(User)에는 없지만 응답에 포함시키고 싶은 가상의 필드를 만듭니다.
    membership_duration = serializers.SerializerMethodField()
    is_newbie = serializers.SerializerMethodField()

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

    # 2. get_<field_name> 메서드 구현 (반드시 이 네이밍 규칙을 지켜야 합니다)
    def get_membership_duration(self, obj):
        """가입 후 며칠이 지났는지 계산 (obj는 현재 직렬화 중인 User 모델 인스턴스)"""
        delta = timezone.now() - obj.date_joined
        return f"가입한 지 {delta.days}일째"
        
    def get_is_newbie(self, obj):
        """작성한 글이 5개 미만이면 True 반환"""
        # obj.posts는 관계된 게시물 역참조 매니저라고 가정
        return obj.posts.count() < 5

필드 옵션 (Arguments)

  • 각 필드에는 데이터의 유효성 검사(Validation) 및 API 문서화를 위한
    • 다양한 옵션(Keyword Arguments)을 부여할 수 있음

필드 레벨 옵션

  • serializers.CharField(), serializers.EmailField()

    • 개별 필드를 선언할 때 괄호 안에 들어가는 파라미터
  • required

    • 데이터 입력 시 해당 필드가 필수인지 여부를 지정
    • 기본값은 True이며, False로 설정하면 클라이언트가 해당 필드를 보내지 않아도 오류가 발생하지 않음
  • allow_null

    • 데이터 값으로 null (파이썬의 None)을 허용할지 여부
    • 프로필 이미지나 자기소개처럼 비어있을 수 있는 데이터에 사용됨
  • write_only

    • 클라이언트가 서버로 데이터를 보낼 때(쓰기)만 사용하고
      • 서버가 응답을 내보낼 때(읽기)는 제외하는 보안 옵션
    • 비밀번호 필드에 필수적으로 사용됨
  • error_messages

    • DRF의 기본 에러 메시지 대신
    • 사용자 친화적인 커스텀 에러 메시지(예: "올바른 이메일 형식이 아닙니다.")를 딕셔너리 형태로 지정 가능
  • max_length / min_length

    • CharField 등에서 입력받을 문자열의 최대/최소 길이를 제한
  • help_text

    • 해당 필드가 어떤 데이터를 의미하는지 설명을 적음
    • 이 옵션은 Swagger나 Redoc 같은 API 문서 자동화 도구에서 필드 설명으로 노출됨
  • style

    • DRF가 제공하는 웹 브라우저블 API(Browsable API) 화면에서
      • 해당 필드가 어떻게 렌더링될지 HTML 입력 폼의 형태를 지정
    • {"input_type": "password"}
      • 설정하면 화면에서 비밀번호가 마스킹 처리되어 보임
  • source

    • 필드를 선언할 때 사용할 수 있는 인자(Argument)로
    • "이 API 필드의 값을 채우기 위해 실제 모델의 어떤 속성(Attribute)이나 메서드(Method)를
      • 참조해야 하는가?"를 DRF에게 알려주는 역할
    • 사용 목적 1
      • API 응답 키(Key)와 DB 필드명을 다르게 할 때
    • 사용 목적 2
      • 관계(Relationship)를 타고 들어가서 특정 데이터만 뽑아올 때 (Dotted Path)

Meta 클래스 전용 옵션

  • ModelSerializer에만 존재하는 Meta 클래스 전용 옵션

    • ModelSerializer는 모델 필드 설정(예: null=True, blank=True)을 읽어와서
    • DRF 필드 옵션(allow_null=True, required=False)으로 자동 추론해 줌
  • model

    • 연결할 Django 모델을 지정
  • fields / exclude

    • 클라이언트와 입출력을 주고받을 모델의 필드들을 리스트 형태로 명시
    • __all__을 쓰면 모든 필드를 가져오지만, 보안상 명시적으로 나열하는 것이 권장
  • extra_kwargs

    • 모델에 정의된 필드 속성을 시리얼라이저 단에서 덮어쓰거나 추가 설정을 부여할 때 사용
    • 모델 필드를 시리얼라이저에서 다시 선언하지 않고도 옵션을 추가할 수 있어서 코드가 깔끔해짐
  • read_only_fields

    • ModelSerializer의 Meta 클래스에서 사용하는 매우 유용한 설정
      • 클라이언트에게 응답으로 데이터를 보여줄 때(Read)는 포함하지만
      • 클라이언트가 데이터를 생성/수정할 때(Write)는 무시하도록 설정하는 기능
    • 사용 목적
      • id, created_at 등 DB나 서버가 자동으로 생성하는 값을 클라이언트가 임의로 조작하지 못하게 막기 위함
      • extra_kwargs = {"field": {"read_only": True}}와 동일한 역할을 하지만
        • 여러 필드를 리스트로 한 번에 처리할 수 있어 코드가 깔끔해짐

예시

  • read_only_fields

    • 설명
      • 클라이언트가 새 게시물(Post)을 작성하기 위해 POST 요청을 보낼 때
      • id나 author 값을 같이 보내더라도 DRF는 이를 완전히 무시하고 모델에 저장하지 않음
      • 오직 title과 content만 저장됩니다.
from rest_framework import serializers
from .models import Post

class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        # 클라이언트와 주고받을 모든 필드 명시
        fields = ['id', 'title', 'content', 'author', 'created_at', 'updated_at']
        
        # [핵심] 수정 불가, 읽기 전용으로 설정할 필드들을 리스트로 나열
        read_only_fields = ['id', 'author', 'created_at', 'updated_at']
  • source

from rest_framework import serializers
from .models import Article

# 가상의 모델 구조:
# User(username, email)
# Article(author=ForeignKey(User), title, desc)

class ArticleSerializer(serializers.ModelSerializer):
    # 1. 필드명 변경 (DB의 'desc' 필드를 API에서는 'description'으로 노출)
    # 클라이언트가 'description'으로 데이터를 보내면 DB의 'desc' 필드에 저장됩니다.
    description = serializers.CharField(source='desc')
    
    # 2. 관계 탐색 (Dotted Path 활용)
    # Article 모델의 'author'(User 모델) 필드에서 'username'만 쏙 빼옵니다.
    # 읽기 전용 데이터이므로 read_only=True를 줍니다.
    writer_name = serializers.CharField(source='author.username', read_only=True)
    
    # 3. 모델의 커스텀 메서드 호출
    # Article 모델에 def is_popular(self): 라는 메서드가 있다면 그 결과값을 가져올 수 있습니다.
    is_popular_article = serializers.BooleanField(source='is_popular', read_only=True)

    class Meta:
        model = Article
        # 클라이언트와 주고받을 최종 API 필드명들을 기재합니다.
        fields = ['id', 'title', 'description', 'writer_name', 'is_popular_article']


# API 응답(JSON) 예시
{
    "id": 1,
    "title": "DRF 마스터하기",
    "description": "source 파라미터에 대한 설명입니다.",  // DB에는 'desc'로 저장되어 있음
    "writer_name": "developer_student",                 // author.username의 결과값
    "is_popular_article": true                          // is_popular() 메서드의 결과값
}

Validation

검증 로직의 순서

  • 클라이언트로부터 데이터를 받아 저장하기 전
    • serializer.is_valid()가 호출될 때 DRF 내부에서는 정해진 순서대로 검증 파이프라인이 실행됨

정해진 순서

  • to_internal_value(self, data)

    • 일반적인 상황에서는 오버라이딩하지 않고 DRF의 기본 로직에 맡김

    • 역할
      • 클라이언트가 보낸 원시 데이터(Raw data, 주로 JSON)를
      • 파이썬의 네이티브 데이터 타입으로 변환(Deserialization)하는 가장 첫 번째 단계
    • 특징
      • 이 단계에서 필드에 설정된
        • 기본 속성들(required=True, max_length, EmailField의 형식 검사 등)이 1차적으로 검증됨
        • 타입 변환에 실패하면 여기서 즉시 ValidationError가 발생
  • validate_<field_name>(self, value) (필드 레벨 검증)

    • 역할
      • to_internal_value를 무사히 통과하여 타입 변환이 완료된 단일 필드의 값을 추가로 정밀 검사
    • 특징
      • 아래의 signup_serializer.pyvalidate_email(self, value)가 바로 이 단계
      • DB 중복 조회나 특정 단어 포함 여부 등을 검사
class EmailSendSerializer(serializers.Serializer):
    email = serializers.EmailField(
        required=True,
        error_messages={
            "invalid": "올바른 이메일 형식이 아닙니다.",
            "required": "이메일을 입력해주세요.",
        },
    )

    def validate_email(self, value):
        if User.objects.filter(email=value).exists():
            raise serializers.ValidationError("이미 가입된 이메일입니다.")
        return value
  • validate(self, data) (객체 레벨 검증)

    • 역할
      • 모든 단일 필드의 검증이 끝난 후, 여러 필드의 값을 조합하여 검증해야 할 때 사용
    • 특징
      • 예를 들어, '비밀번호'와 '비밀번호 확인' 필드가 서로 일치하는지
      • 혹은 '시작일'이 '종료일'보다 빠른지 등을 비교할 때 필수적

예시

from rest_framework import serializers

class UserRegistrationSerializer(serializers.Serializer):
    username = serializers.CharField(max_length=20)
    password = serializers.CharField()
    password_confirm = serializers.CharField()

    # 1. (참고용) 일반적인 상황에서는 오버라이딩하지 않고 DRF의 기본 로직에 맡깁니다.
    def to_internal_value(self, data):
        """1단계: 입력 데이터를 파이썬 타입으로 변환 및 기본 필드 속성 검증"""
        # 아주 특수한 전처리가 필요한 경우에만 오버라이딩합니다.
        # 예: 클라이언트가 보낸 모든 문자열의 공백을 제거
        return super().to_internal_value(data)

    # 2. 필드 레벨 검증
    def validate_username(self, value):
        """2단계: 'username' 필드 단독 검증"""
        if "admin" in value.lower():
            raise serializers.ValidationError("관리자를 사칭하는 닉네임은 사용할 수 없습니다.")
        return value

    # 3. 객체 레벨 검증
    def validate(self, data):
        """3단계: 여러 필드를 함께 묶어서 검증"""
        # data에는 to_internal_value와 단일 필드 검증을 통과한 안전한 딕셔너리가 들어있습니다.
        if data['password'] != data['password_confirm']:
            raise serializers.ValidationError({
                # 특정 필드에 에러 메시지를 할당할 수 있습니다.
                "password_confirm": "비밀번호가 일치하지 않습니다." 
            })
        return data
profile
안녕하세요.

0개의 댓글