[Django/DB] Django REST framework - N:1 Relation

문지은·2023년 5월 14일
0

Django + Database

목록 보기
11/12
post-thumbnail

N:1 관계에서의 모델 data를 Serialization 하여 JSON으로 변환하는 방법에 대해 알아보자.

사전 준비

  • Comment 모델 작성 및 데이터베이스 초기화 & migration 진행
class Comment(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE)
    content = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
  • 준비된 fixtures 데이터 load
$ python manage.py loaddata articles.json comments.json

GET - List

  • 댓글 데이터 목록 조회하기
  • Article List와 비교하며 작성해보기
# articles/serializers.py

from .models import Article, Comment

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

urlpatterns = [
    ...,
    path('comments/', views.comment_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 응답 확인

GET - Detail

  • 단일 댓글 데이터 조회하기
  • Article과 달리 같은 Serializer 사용하기
    • 조회하고자 하는 필드가 다를 경우 별도의 Serializer을 작성하여 사용해도 된다.
# articles/urls.py

urlpatterns = [
    ...,
    path('comments/<int:comment_pk>/', views.comment_detail),
]
# articles/views.py

@api_view(['GET'])
def comment_detail(request, comment_pk):
    if request.method == 'GET':
        comment = Comment.objects.get(pk=comment_pk)
        serializer = CommentSerializer(comment)
        return Response(serializer.data)
  • GET 응답 확인

POST

  • 단일 댓글 데이터 생성하기
# articles/urls.py

urlpatterns = [
    ...,
    path('articles/<int:article_pk>/comments/', views.comment_create),
]
# 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)

Passing Additional attributes to .save()

  • save() 메서드는 특정 Serializer 인스턴스 저장하는 과정에서 추가적인 데이터를 받을 수 있음
  • CommentSerializer를 통해 Serializer되는 과정에서 Parameter로 넘어온 article_pk에 해당하는 article 객체를 추가적인 데이터를 넘겨 저장

읽기 전용 필드 설정

  • read_only_fields를 사용해 외래 키 필드를 ‘읽기 전용 필드’로 설정
  • 읽기 전용 필드는 데이터를 전송하는 시점에 ‘해당 필드를 유효성 검사에서 제외시키고 데이터 조회 시에는 출력’하도록 함
# articles/serializers.py

class CommentSerializer(serializers.ModelSerializer):
    class Meta:
        model = Comment
        fields = '__all__'
        read_only_fields = ('article', )
  • 설정하지 않으면 CommentSerializer에서 article field 데이터 또한 사용자로부터 입력받도록 설정되어 에러 발생

  • POST 응답 확인

DELETE & PUT

  • 댓글 데이터 삭제 및 수정 구현하기
# articles/views.py

@api_view(['GET', 'DELETE', 'PUT'])
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 == 'DELETE':
        comment.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)
    
    elif request.method == 'PUT':
        serializer = CommentSerializer(comment, data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data)
  • DELETE 응답 확인

  • PUT 응답 확인

  • 삭제 후 메시지 출력하기
@api_view(['GET', 'DELETE', 'PUT'])
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 == 'DELETE':
        comment.delete()
        data = {
            'delete': f'댓글 {comment_pk}번이 삭제되었습니다.',
        }
        return Response(data, status=status.HTTP_204_NO_CONTENT)
    
    elif request.method == 'PUT':
        serializer = CommentSerializer(comment, data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data)

N:1 역참조 데이터 조회

특정 게시글에 작성된 댓글 목록 출력하기

  • 기존 필드 override - Article Detail
    • 게시글 조회 시 해당 게시글의 댓글 목록까지 함께 출력하기
    • Serializer는 기존 필드를 override 하거나 추가적인 필드를 구성할 수 있음

PrimaryKeyRelatedField()

# articles/serializers.py

class ArticleSerializer(serializers.ModelSerializer):

    # 역참조 데이터 조회
    comment_set = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    
    class Meta:
        model = Article
        fields = '__all__'
  • 댓글이 있는 게시글 응답 예시

  • models.py에서 related_name을 통해 이름 변경 가능
    • 역참조시 생성되는 comment_set을 override 할 수 있음
    • 지금은 필요 없으므로 작성해보고 삭제하자~
      # articles/models.py
      
      class Comment(models.Model):
          article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
          content = models.TextField()
          created_at = models.DateTimeField(auto_now_add=True)
          updated_at = models.DateTimeField(auto_now=True)

Nested relationships

  • 모델 관계 상으로 참조 된 대상은 참조하는 대상의 표현에 포함되거나 중첩(nested)될 수 있음
  • 이러한 중첩된 관계는 serializers를 필드로 사용하여 표현할 수 있음
  • 두 클래스의 상/하 위치를 변경해야 함
# articles/serializers.py

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

class ArticleSerializer(serializers.ModelSerializer):
    comment_set = CommentSerializer(many=True, read_only=True)
    class Meta:
        model = Article
        fields = '__all__'
  • 댓글이 있는 게시글 응답 예시

특정 게시글에 작성된 댓글의 개수 출력하기

  • 새로운 필드 추가 - Article Detail
  • 게시글 조회시 해당 게시글의 댓글 개수 까지 함께 출력하기
# articles/serializers.py

class ArticleSerializer(serializers.ModelSerializer):

    comment_set = CommentSerializer(many=True, read_only=True)
    comment_count = serializers.IntegerField(source='comment_set.count', read_only=True)
    
    class Meta:
        model = Article
        fields = '__all__'
  • 댓글이 있는 게시글 응답 예시

source

  • serializers field’s argument
  • 필드를 채우는 데 사용할 속성의 이름
  • 점 표기법(dotted notation)을 사용하여 속성을 탐색할 수 있음

[주의] 읽기 전용 필드 지정 이슈

  • 특정 필드를 override 혹은 추가한 경우 read_only_fields가 동작하지 않으니 주의한다.
# 사용 불가능

class ArticleSerializer(serializers.ModelSerializer):

    comment_set = CommentSerializer(many=True)
    comment_count = serializers.IntegerField(source='comment_set.count')
    
    class Meta:
        model = Article
        fields = '__all__'

📍 프로젝트 전체 코드 확인하기

profile
코드로 꿈을 펼치는 개발자의 이야기, 노력과 열정이 가득한 곳 🌈

0개의 댓글