본문 바로가기

TIl

REST API 2

DRF with N:1 Relation

GET - List

  • 댓글 목록 조회를 위한 CommentSerializer 정의
# serializers.py
class CommentSerializer(serializers.ModelSerializer):

    class Meta:
        model = Comment
        fields = '__all__'
  • url
# urls.py
urlpatterns = [
    path('comments/', views.comment_list),
]

  • view 함수
# views.py
@api_view(['GET',])
def comment_list(request):
    # 전체 댓글 조회
    comments = Comment.objects.all()
    
    # 직렬화 진행, QuerySet을 다루기 때문에 many=True
    serializer = CommentSerializer(comments, many=True)
    return Response(serializer.data)

Get - Detail

  • 단일 댓글 조회
urlpatterns = [
    path('comments/<int:commnet_pk>/', views.comment_detail),
]
@api_view(['GET',])
def comment_detail(request, comment_pk):
    # 전체 댓글 조회
    comment = Comment.objects.get(pk=comment_pk)
    
    # 직렬화 진행
    serializer = CommentSerializer(comment)
    return Response(serializer.data)

POST

  • 단일 댓글 생성
# urls.py
urlpatterns = [
    path('articles/<int:article_pk>/comments/', views.comment_create)
]
# views.py 400에러 발생
@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()
        return Response(serializer.data, status=status.HTTP_201_CREATED)
  • seralizer 인스턴스의 save() 메서드는 특정 Serializer 인스턴스를 저장하는 과정에서 추가 데이터를 받을 수 있음
# views.py 400 에러 여전히 발생
# 유효성 검사 
    if serializer.is_valid(raise_exception=True):
        serializer.save(article=article) # 이 부분 추가
        return Response(serializer.data, status=status.HTTP_201_CREATED)
  • 상태코드 400 응답 확인
  • CommentSerializer에서 외래 키에 해당하는 article field 또한 사용자로부터 입력 받도록 설정되어 있기 때문에 서버 측에서는 누락되었다고 판단한 것
  • 유효성 검사 목록에서 제외 필요
  • article field를 읽기 전용 필드로 설정하기

읽기 전용 필드 ( read_ondly_fields )

  • 데이터를 전송 받은 시점에 유효성 검사에서 제외시키고, 데이터 조회 시에는 출력 하는 필드
# serializers.py
class CommentSerializer(serializers.ModelSerializer):

    class Meta:
        model = Comment
        fields = '__all__'
        read_only_fields = ('article',)

DELETE & PUT

  • 단일 댓글 삭제 및 수정을 위한 view 함수 작성
# views.py
@api_view(['GET','DELETE','PUT'])
def comment_detail(request, comment_pk):
    # 단일 댓글 조회
    comment = Comment.objects.get(pk=comment_pk)
    if request.method == 'GET':
       ...    
    # 삭제
    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, status=status.HTTP_200_OK)

응답 데이터 재구성

댓글 조회 시 게시글 출력 내역 변경

  • 댓글 조회 시 게시글 번호만 제공해주는 것이 아닌 ‘게시글의 제목’까지 제공하기
  • 필요한 데이터를 만들기 위한 Serializer는 내부에서 추가 선언이 가능
# 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',)

역참조 데이터 구성

Article → Comment 간 역참조 관계를 활용한 JSON 데이터 재구성

아래 2가지 사항에 대한 데이터 재구성하기

  1. 단일 게시글 조회 시 해당 게시글에 작성된 댓글 목록도 함께 붙여서 응답
  2. 단일 게시글 조회 시 해당 게시글에 작성된 댓글 개수도 함께 붙여서 응답

1. 단일 게시글 + 댓글 목록

  • Nested relationships ( 역참조 매니저 활용)
    • 모델 관계 상으로 참조하는 대상은 참조하는 대상의 표현에 포함되거나 중첩될 수 있음
    • 이러한 중첩된 관계는 serializers를 필드로 사용하여 표현 가능
    # serializers.py
    class ArticleSerializer(serializers.ModelSerializer):
    
        class CommentDetailSerializer(serializers.ModelSerializer):
            class Meta:
                model = Comment
                fields = ('id','content',)
                # read_only_fields = ('article',)
    
        # 필드상으로 존재하지 않기때문에 직접 추가해줘야함
        comment_set = CommentDetailSerializer(read_only=True, many=True)
    
        class Meta:
            model = Article
            fields = '__all__'
    

2. 단일 게시글 + 댓글 개수

  • 댓글 개수에 해당하는 새로운 필드 생성
# 필드 상에도 존재하지 않고, 실제로도 없어서 이름이 정해져있지 않음 => 새로운 필드
comment_count = serializers.IntegerField(source='comment_set.count', read_only = True)

source arguments

  • 필드를 채우는 데 사용할 속성의 이름
  • 점 표기법(dotted notation)을 사용하여 속성을 탐색 할 수 있음
  • source='comment_set.count'

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

  • 특정 필드를 override 혹은 추가한 경우 read_only_fields는 동작하지 않음
  • ⇒ 이런 경우 새로운 필드에 read_only 키워드 인자로 작성해야 함
class CommentSerializer(serializers.ModelSerializer):
	...
	
class ArticleSerializer(serializers.ModelSerializer):
    comment_set = CommentDetailSerializer(read_only=True, many=True)
    comment_count = serializers.IntegerField(source='comment_set.count', read_only = True)

    class Meta:
        model = Article
        fields = '__all__'
        # read_only_fields = ('comment_set','comment_count',) # 동작 X

API 문서화

OpenAPI Specification OAS

RESTful API를 설명하고 시각화하는 표준화된 방법

⇒ API에 대한 세부사항을 기술할 수 있는 공식 표준

Swagger, Redoc

OAS 기반 API에 대한 문서를 생성하는 데 도움을 주는 오픈소스 프레임워크

drf-spectacular 라이브러리

  • DRF 위한 OpenAPI 3.0 구조 생성을 도와주는 라이브러리
  • 설치 및 등록
  • pip install drf-spectacular
  • setting.py에 앱 등록
# settings.py
INSTALLED_APPS = [
		...
    'drf_spectacular',
]

# 관련 설정 코드 입력 (OpenAPI 구조 자동 생성 코드) 복사해오는것을 추천
REST_FRAMEWORK = {
    # YOUR SETTINGS
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}
  • swagger, redoc 페이지 제공을 위한 url 작성
# procjet/urls.py

from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
urlpatterns = [
    # YOUR PATTERNS
    path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
    # Optional UI:
    path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
    path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
]

필수가 아니지만, 커스텀 가능한 세팅

# settings.py

SPECTACULAR_SETTINGS = {
    'TITLE': '내 API 서비스',
    'DESCRIPTION': 'Django 마지막 서비스',
    'VERSION': '1.0.0',
    'SERVE_INCLUDE_SCHEMA': False,
    # OTHER SETTINGS
}

“설계 우선” 접근법

  • OAS의 핵심 이점
  • API를 먼저 설계하고 명세를 작성한 후, 이를 기반으로 코드를 구현하는 방식
  • API의 일관성을 유지하고, API 사용자는 더 쉽게 API를 이해하고 사용할 수 있음
  • 또한, OAS를 사용하면 API가 어떻게 작동하는지를 시각적으로 보여주는 문서를 생성할 수 있으며, 이는 API를 이해하고 테스트하는 데 매우 유용
  • 이런 목적으로 사용되는 도구가 Swagger-UI 또는 ReDoc

참고

Django shortcuts functions

  • render()
  • redirect()
  • get_object_or_404()
  • get_list_or_404()

게시글이 존재하지 않으면 터지는 문제, 어떻게 해결 할 수 있을 까?

# views.py
try:
    article = Article.objects.get(pk=article_pk)
except DoesNotExist:
    return HTTP.404
    

get_object_or_404()

모델 manager objects에서 get()을 호출하지만, 해당 객체가 없을 땐 기존 DoesNotExist 예외 대신 Http404를 raise함

적용

# views.py
**from django.shortcuts import get_object_or_404**

# article = Article.objects.get(pk=article_pk)
    article = get_object_or_404(Article,pk=article_pk)
    
# comment = Comment.objects.get(pk=comment_pk)
    comment = get_object_or_404(Comment,pk=comment_pk)

get_list_or_404()

모델 manager objects에서 filter()의 결과를 반환하고, 해당 객체 목록이 없을 땐 Http404를 raise함

적용

# views.py
**from django.shortcuts import get_list_or_404

# articles = Article.objects.all()
        articles = get_list_or_404(Article)
        
# comments = Comment.objects.all()
    comments = get_list_or_404(Comment)**

적용 전 : 상태 코드 500

적용 후 : 상태 코드 404

왜 사용해야 될까?

  • 클라이언트에게 “서버에 오류가 발생하여 요청을 수행할 수 없다(500)”라는 원인이 정확하지 않은 에러를 제공하기 보다는, 적절한 예외 처리를 통해 클라이언트에게 보다 정확한 에러 현황을 전달하는 것도 매우 중요한 개발 요소 중 하나이기 때문

'TIl' 카테고리의 다른 글

N:M 1  (1) 2024.04.22
DOM  (1) 2024.04.16
REST API 1  (0) 2024.04.16
SQL  (1) 2024.04.16
Authentication 2  (0) 2024.04.16