TIL 05-25 DRF 2

김덕협·2026년 5월 25일

TIL

목록 보기
13/41

Django REST Framework 2 : N:1 관계에서의 DRF 구현

1. GET method

GET - list (전체 댓글 조회)

# articles/views.py
from .models import Article, Comment
from .serializers import ArticleListSerializer, ArticleSerializer, CommentSerializer

@api_view(['GET'])
def comment_list(request):
    comments = Comment.objects.all()
    serializer = CommentSerializer(comments, many=True)
    return Response(serializer.data)

GET - detail (단일 댓글 조회)

# articles/views.py
@api_view(['GET'])
def comment_detail(request, comment_pk):
    # 특정 댓글 데이터를 조회
    comment = Comment.objects.get(pk=comment_pk)
    serializer = CommentSerializer(comment)
    return Response(serializer.data)

2. POST method (댓글 생성)

# articles/views.py
@api_view(['POST'])
def comment_create(request, article_pk):
    # 어떤 게시글에 작성되는 댓글인지 단일 게시글을 조회
    article = Article.objects.get(pk=article_pk)
    serializer = CommentSerializer(data=request.data)
    if serializer.is_valid(raise_exception=True):
        serializer.save(article=article)
        return Response(serializer.data, status=status.HTTP_201_CREATED)

⚠️ 400 Bad Request 문제

위 코드만으로는 400 Bad Request가 발생한다.

원인: CommentSerializer에서 외래 키에 해당하는 article 필드 또한 사용자로부터 입력받도록 설정되어 있기 때문에, 서버 측에서는 해당 필드가 누락되었다고 판단함.

해결: 유효성 검사 목록에서 제외 → article 필드를 읽기 전용 필드로 설정

# articles/serializers.py
class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = '__all__'
        read_only_fields = ('article',)

3. DELETE & PUT method (댓글 삭제 및 수정)

# articles/views.py
@api_view(['GET', 'PUT', 'DELETE'])
def comment_detail(request, comment_pk):
    comment = Comment.objects.get(pk=comment_pk)

    if request.method == 'GET':
        serializer = CommentSerializer(comment)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = CommentSerializer(comment, data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data)

    elif request.method == 'DELETE':
        comment.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

4. 응답 데이터 재구성 (중첩 Serializer)

댓글 목록 조회 시 article의 pk 뿐만 아니라 게시글 제목(title)까지 함께 응답하기

CommentSerializer 내부에 ArticleTitleSerializer를 별도로 정의하여 articletitle만 뽑아오도록 구현한다.

# articles/serializers.py
class CommentSerializer(serializers.ModelSerializer):
    class ArticleTitleSerializer(serializers.ModelSerializer):
        class Meta:
            model = Article
            fields = ('title',)

    article = ArticleTitleSerializer(read_only=True)

    class Meta:
        model = Comment
        fields = '__all__'
        # ❌ read_only_fields = ('article',)
        # 특정 필드를 재정의 또는 추가한 경우 read_only_fields는 작동하지 않음
        # → 새로운 필드에 read_only 키워드 인자로 직접 작성해야 함

5. 역참조 데이터 구성

Article → Comment 간 역참조 구성을 활용하여 JSON 데이터를 재구성해보자.

단일 게시글 조회 시 댓글 목록도 함께 응답

# articles/serializers.py
class ArticleSerializer(serializers.ModelSerializer):
    class CommentDetailSerializer(serializers.ModelSerializer):
        class Meta:
            model = Comment
            fields = ('id', 'content',)

    comment_set = CommentDetailSerializer(many=True, read_only=True)

    class Meta:
        model = Article
        fields = '__all__'

단일 게시글 조회 시 댓글 개수도 함께 응답

기본적으로 게시글을 조회하면 참조 중인 댓글의 개수는 알 수 없다. Article 모델에 댓글 개수를 저장하는 필드를 지정하지 않았기 때문에, 직접 계산해야 한다.

annotate 사용

# articles/views.py
from django.db.models import Count

@api_view(['GET', 'DELETE', 'PUT'])
def article_detail(request, article_pk):
    article = Article.objects.annotate(num_of_comments=Count('comment')).get(pk=article_pk)
# articles/serializers.py
class ArticleSerializer(serializers.ModelSerializer):
    ...
    num_of_comments = serializers.SerializerMethodField()

    class Meta:
        ...

    def get_num_of_comments(self, obj):
        # obj는 Serializer가 처리하는 Article 인스턴스
        # view에서 annotate 한 필드를 그대로 사용 가능
        return obj.num_of_comments

obj는 현재 직렬화 중인 모델 인스턴스이며, 이 메서드에서 obj의 속성이나 annotate된 필드를 활용해 새 값을 만들 수 있음.




6. SerializerMethodField

Serializer에서 추가적인 데이터 가공을 하고 싶을 때 사용. (비율, 합계, 평균 등)

동작 원리

  • SerializerMethodField를 Serializer 클래스 내에서 필드로 선언하면, DRF는 get_<필드명>이라는 이름을 가진 메서드를 자동으로 찾음
  • 예를 들어, full_name = serializers.SerializerMethodField()라고 선언하면, DRF는 get_full_name(self, obj) 메서드를 찾아 해당 값을 직렬화 결과에 넣어 줌
class UserSerializer(serializers.ModelSerializer):
    full_name = serializers.SerializerMethodField()

    class Meta:
        model = User
        fields = ('id', 'username', 'full_name', 'email',)

    def get_full_name(self, obj):
        return f'{obj.first_name} {obj.last_name}'

⚠️ 주의사항

  • 읽기 전용 — 생성(POST), 수정(PUT) 요청 시에는 사용되지 않음
  • get_ 메서드는 반드시 (self, obj) 형태로 정의해야 하며, obj는 현재 직렬화 중인 모델 인스턴스를 의미

사용 목적

목적설명
유연성다양한 계산 로직을 손쉽게 추가 가능
가독성데이터 변환 과정을 Serializer 내부 메서드로 명확히 분리
유지보수성view나 model에 비해 Serializer 측 로직 변경이 용이
일관성view에서 별도로 data 수정 없이도 직렬화 결과를 제어
profile
뭘봐

0개의 댓글