Django DRF Serializer와 View를 활용한 데이터 직렬화/역직렬화 패턴

View 기반 API 구현 템플릿

# views.py
from django.views import View
from django.http import JsonResponse
import json

class ProjectManageView(View):
    """
    GET: 전체 프로젝트 조회 (직렬화)
    POST: 새 프로젝트 생성 (역직렬화)
    """
    
    def retrieve(self, request):
        """전체 데이터 조회 및 직렬화 출력"""
        # 1. ORM으로 쿼리셋 획득
        queryset = Project_t.objects.all().order_by('-create_time')
        
        # 2. instance 파라미터로 직렬화 객체 생성 (다중 객체는 many=True 필수)
        serializer = serializerstest.ProjectSerializer(instance=queryset, many=True)
        
        # 3. .data 속성으로 직렬화 결과 획득 후 응
        return JsonResponse(serializer.data, safe=False, json_dumps_params={'ensure_ascii': False})
    
    def create(self, request):
        """요청 데이터 역직렬화 및 저장"""
        # 1. 바이트 스트림 → 문자열 → 파이썬 dict 변환
        payload = json.loads(request.body.decode('utf-8'))
        
        # 2. data 파라미터로 역직렬화 객체 생성 (검증용)
        deserializer = serializerstest.ProjectSerializer(data=payload)
        
        # 3. is_valid() 호출로 유효성 검증 실행
        if not deserializer.is_valid(raise_exception=False):
            return JsonResponse(
                {'error': deserializer.errors}, 
                status=400, 
                json_dumps_params={'ensure_ascii': False}
            )
        
        # 4. 검증 통과 데이터로 ORM 객체 생성
        new_project = Project_t.objects.create(**deserializer.validated_data)
        
        # 5. 생성된 객체 다시 직렬화하여 응답
        response_serializer = serializerstest.ProjectSerializer(instance=new_project)
        
        return JsonResponse(
            response_serializer.data, 
            status=201, 
            json_dumps_params={'ensure_ascii': False}
        )

직렬화 vs 역직렬화 파라미터 비교

구분 역직렬화 (입력) 직렬화 (출력)
핵심 파라미터 data=dict instance=Model
데이터 방향 클라이언트 → 서버 서버 → 클라이언트
주요 메서드 .is_valid(), .validated_data .data
검증 여부 필드 규칙 기반 자동 검증 검증 없음, 포맷 변환만 수행

전체 데이터 흐름도

# === 클라이언트 요청 단계 ===
# Content-Type: application/json
# Body: {"name": "AI 플랫폼", "leader": "김철수", "is_execute": true, "desc": "머신러닝 기반 서비스"}

# 1. 역직렬화: JSON → Python dict → 검증
incoming = json.loads(request.body.decode('utf-8'))
# incoming: {'name': 'AI 플랫폼', 'leader': '김철수', 'is_execute': True, 'desc': '머신러닝 기반 서비스'}

checker = ProjectSerializer(data=incoming)
checker.is_valid(raise_exception=True)  # 필드 규칙, validators, 단일 필드 검증 순으로 실행

# 2. 데이터베이스 반영
project_record = Project_t.objects.create(**checker.validated_data)
# project_record: <Project_t: id=7, name='AI 플랫폼'...>

# 3. 직렬화: ORM 객체 → JSON 응답
formatter = ProjectSerializer(instance=project_record)
response_payload = formatter.data
# response_payload: {'id': 7, 'name': 'AI 플랫폼', 'leader': '김철수', 'desc': '머신러닝 기반 서비스', 'create_time': '2024-01-15 09:30:00'}

모델 정의

# models.py
from django.db import models
from utils.base_model import BaseModel

class Project_t(BaseModel):
    name = models.CharField(
        max_length=50, 
        unique=True,
        verbose_name="프로젝트명",
        help_text="고유한 프로젝트 이름 입력"
    )
    leader = models.CharField(
        max_length=50,
        verbose_name="담당자",
        help_text="프로젝트 책임자 이름"
    )
    is_execute = models.BooleanField(
        default=False,
        verbose_name="실행 여부",
        help_text="프로젝트 시작 상태 선택"
    )
    desc = models.TextField(
        max_length=125,
        blank=True, 
        null=True,
        verbose_name="상세 설명",
        help_text="프로젝트에 대한 부가 설명"
    )

    class Meta:
        db_table = "tb_project_test"
        verbose_name = "프로젝트"
        ordering = ['-id']

    def __str__(self):
        return f'[{self.id}] {self.name}'

Serializer 상세 구현

# serializers.py
from rest_framework import serializers, validators
from . import modelstest


# ============ 커스텀 검증 함수 (모듈 레벨) ============

def check_name_has_keyword(val):
    """프로젝트명 필수 키워드 검증"""
    if '플랫폼' not in val:
        raise serializers.ValidationError("프로젝트명에 '플랫폼'을 포함해야 합니다")
    return val


def check_leader_format(val):
    """담당자명 형식 검증"""
    if len(val) < 2:
        raise serializers.ValidationError("담당자명은 2자 이상이어야 합니다")
    return val


# ============ Serializer 클래스 ============

class ProjectSerializer(serializers.Serializer):
    """
    Serializer 사용 목적:
    - ORM 객체 → JSON 변환 (직렬화)
    - JSON → Python 객체 변환 및 검증 (역직렬화)
    - 비즈니스 규칙을 분리된 계층에서 관리
    """
    
    # --- 출력 전용 필드 (read_only=True) ---
    id = serializers.IntegerField(
        read_only=True,
        min_value=1,
        label='프로젝트 ID',
        help_text='자동 생성되는 고유 식별자'
    )
    
    create_time = serializers.DateTimeField(
        read_only=True,
        format='%Y-%m-%d %H:%M:%S',
        label='등록일시'
    )
    
    # --- 입출력 공통 필드 ---
    name = serializers.CharField(
        max_length=100,
        min_length=2,
        required=True,
        label='프로젝트명',
        help_text='2~100자 사이의 프로젝트 이름',
        validators=[
            check_name_has_keyword,
            validators.UniqueValidator(
                queryset=modelstest.Project_t.objects.all(),
                message="이미 등록된 프로젝트명입니다"
            )
        ],
        error_messages={
            'required': '프로젝트명은 필수 입력값입니다',
            'min_length': '프로젝트명은 최소 2자 이상 입력하세요',
            'max_length': '프로젝트명은 최대 100자까지 가능합니다'
        }
    )
    
    leader = serializers.CharField(
        max_length=100,
        required=True,
        allow_blank=False,
        trim_whitespace=True,  # 좌우 공백 자동 제거
        validators=[check_leader_format],
        error_messages={
            'required': '당자를 지정해야 합니다',
            'null': '담당자는 null일 수 없습니다'
        }
    )
    
    desc = serializers.CharField(
        max_length=255,
        required=True,
        help_text='프로젝트에 대한 상세 설명'
    )
    
    # --- 입력 전용 필드 (write_only=True) ---
    is_execute = serializers.BooleanField(
        write_only=True,
        required=False,
        default=False,
        help_text='프로젝트 즉시 실행 여부 (응답 미포함)'
    )
    
    # ============ 단일 필드 검증 (validate_<field_name>) ============
    
    def validate_name(self, value):
        """
        name 필드의 모든 validators 통과 후 실행
        복잡한 비즈니스 규칙을 여기에 배치
        """
        if value.count(' ') > 0:
            raise serializers.ValidationError("프로젝트명에 공백을 포함할 수 없습니다")
        if not any(c.isdigit() for c in value):
            raise serializers.ValidationError("프로젝트명에 숫자를 1개 이상 포함해야 합니다")
        return value
    
    def validate_desc(self, value):
        """desc 필드 추가 검증"""
        forbidden_words = ['금지', '불가']
        for word in forbidden_words:
            if word in value:
                raise serializers.ValidationError(f"설명에 '{word}'(은)는 사용할 수 없습니다")
        return value
    
    # ============ 다중 필드 교차 검증 ============
    
    def validate(self, attrs):
        """
        모든 단일 필드 검증 완료 후 실행
        필드 간 의존성 검증에 활용
        """
        if attrs.get('is_execute') and not attrs.get('leader'):
            raise serializers.ValidationError({
                'is_execute': '프로젝트 실행 시 담당자는 필수입니다'
            })
        return attrs

핵심 필드 파라미터 정리

파라미터 역할 주의사항
read_only=True 응답에만 포함, 입력 무시 required와 동시 사용 시 read_only 우선
write_only=True 요청에만 포함, 응답 제외 비밀번호 등 민감 정보에 활용
required=False 선택 입력 필드로 설정 default 미설정 시 None 할당 가능
default=<value> 미입력 시 기본값 적용 required=True와 동시 사용 불가
allow_null=True null 값 허용 CharField 등에서 None vs "" 구분
allow_blank=True 빈 문자열 허용 CharField, TextField 전용
trim_whitespace=True 좌우 공백 자동 제거 기본값 True, CharField 적용

검증 우선순위 및 흐름

입력 데이터: {"name": "플랫폼 2024", "leader": "  홍길동  ", "desc": "테스트", "is_execute": true}

[1단계: 필드 타입 검증]
  → CharField, BooleanField 등 선언 타입과 일치 여부 확인
  
[2단계: 필드 수준 validators 실행]
  → max_length, min_length 등 내장 규칙
  → validators=[...]에 등록된 커스텀 함수들
  
[3단계: 단일 필드 validate_<field> 메서드]
  → def validate_name(self, value): ...
  → def validate_leader(self, value): ...
  
[4단계: 객체 수준 validate 메서드]
  → def validate(self, attrs): ...
  → 여러 필드 간 논리 검증

[5단계: 검증 결과 접근]
  serializer.is_valid()  # True 시
  → serializer.validated_data  # 검증 통과 데이터 (dict)
  → serializer.errors      # 검증 실패 시 오류 정보

save() 메서드 활용한 간결 패턴

class ProjectManageView(View):
    
    def post(self, request):
        payload = json.loads(request.body.decode('utf-8'))
        
        # create/update 통합 처리
        serializer = ProjectSerializer(data=payload)
        serializer.is_valid(raise_exception=True)
        
        # ModelSerializer 미사용 시 create() 메서드 오버라이드 필요
        instance = serializer.save()  # 내부적으로 create() 또는 update() 호출
        
        # 저장된 객체 즉시 직렬화
        return JsonResponse(
            ProjectSerializer(instance=instance).data,
            status=201,
            json_dumps_params={'ensure_ascii': False}
        )

Serializer 내부 create/update 커스텀

class ProjectSerializer(serializers.Serializer):
    # ... 필드 정의 ...
    
    def create(self, validated_data):
        """
        .save() 호출 시 새 객체 생성 로직
        ModelSerializer 자동 구현 vs Serializer 수동 정의
        """
        # validated_data.pop('is_execute')  # write_only 필드 제외 등 커스텀 가능
        return Project_t.objects.create(**validated_data)
    
    def update(self, instance, validated_data):
        """
        기존 객체 수정 로직
        """
        instance.name = validated_data.get('name', instance.name)
        instance.leader = validated_data.get('leader', instance.leader)
        instance.desc = validated_data.get('desc', instance.desc)
        instance.save()
        return instance

태그: Django DRF Serializer View API Development

6월 30일 03:32에 게시됨