Serializer 📌
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):
membership_duration = serializers.SerializerMethodField()
is_newbie = serializers.SerializerMethodField()
class Meta:
model = User
fields = ['username', 'email', 'membership_duration', 'is_newbie']
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 반환"""
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)
ModelSerializer는 모델 필드 설정(예: null=True, blank=True)을 읽어와서
- DRF 필드 옵션(
allow_null=True, required=False)으로 자동 추론해 줌
model
fields / exclude
- 클라이언트와 입출력을 주고받을 모델의 필드들을 리스트 형태로 명시
__all__을 쓰면 모든 필드를 가져오지만, 보안상 명시적으로 나열하는 것이 권장
- 모델에 정의된 필드 속성을 시리얼라이저 단에서 덮어쓰거나 추가 설정을 부여할 때 사용
- 모델 필드를 시리얼라이저에서 다시 선언하지 않고도 옵션을 추가할 수 있어서 코드가 깔끔해짐
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']
from rest_framework import serializers
from .models import Article
class ArticleSerializer(serializers.ModelSerializer):
description = serializers.CharField(source='desc')
writer_name = serializers.CharField(source='author.username', read_only=True)
is_popular_article = serializers.BooleanField(source='is_popular', read_only=True)
class Meta:
model = Article
fields = ['id', 'title', 'description', 'writer_name', 'is_popular_article']
{
"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.py의 validate_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()
def to_internal_value(self, data):
"""1단계: 입력 데이터를 파이썬 타입으로 변환 및 기본 필드 속성 검증"""
return super().to_internal_value(data)
def validate_username(self, value):
"""2단계: 'username' 필드 단독 검증"""
if "admin" in value.lower():
raise serializers.ValidationError("관리자를 사칭하는 닉네임은 사용할 수 없습니다.")
return value
def validate(self, data):
"""3단계: 여러 필드를 함께 묶어서 검증"""
if data['password'] != data['password_confirm']:
raise serializers.ValidationError({
"password_confirm": "비밀번호가 일치하지 않습니다."
})
return data