>

TIL

10주차 Day 5. DRF 단일모델 CRUD, CBV

ekdud 2024. 8. 30. 17:02

📑

     

    Django REST Framework (DRF)

    DRF 설치 및 설정

    pip install djangorestframework
    
    pip freeze > requirements.txt

     

    그리고 settings.py의 INSTALLED_APPS에 추가해 주고, DRF를 사용하기 위해 우선 각각의 앱마다 serializers.py 파일을 생성한다.

     

     

     

     

    serializers를 내가 원하는 형태로 여러 가지 만들어 놓고 원할 때 바꿔 끼워 쓸 수 있도록 함.

    from rest_framework import serializers
    from .models import Article
    
    class ArticleSerializer(serializers.ModelSerializer):
        class Meta:
            model = Article
            fields = "__all__"
    from django.shortcuts import render
    from django.http import JsonResponse, HttpResponse
    from django.core import serializers
    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from .models import Article
    from .serializers import ArticleSerializer
    
    ...
    
    @api_view(["GET"])
    def json_drf(request):
        articles = Article.objects.all()
        # articles가 단일객체가 아니기 때문에 many=True를 넣어줘야 함.
        serializer = ArticleSerializer(articles, many=True)
        return Response(serializer.data)

     

    이렇게 하면 전에 django serializers를 사용하거나 JsonResponse로 직렬화했을 때와는 또 다르게 보여진다. ↴

    runserver - json_drf

     

     

     

     

     

    Postman

    : API를 호출하고 결과도 볼 수 있게 해주는 프로그램.  Postman 가입 및 설치🔗

    API 테스트, 환경관리, 협업 등을 위한 강력한 기능을 제공한다. (API 디자인, 테스트, 문서화, 공유 등)

     

     


     

    DRF Single Model

    단일모델에서 데이터를 조회해서 직렬화(serialization)하여 JSON으로 응답하기.

    ModelSerializer - Model의 여러 가지 필드들을 어떻게 직렬화해서 (어떤 데이터 포맷으로) response를 줄지가 핵심. ModelForm의 사용과 굉장히 유사하다 !

     

    API 설계

    ex.

      Method Endpoint
    Article 목록조회 GET /articles/
    Article 상세조회 GET /articles/<int:article_id>/
    Article 생성 POST /articles/
    Article 수정 PUT /articles/<int:article_id>/
    Article 삭제 DELETE /articles/<int:article_id>/

     

     

     

     

     

    @api_view()

    # article 목록 조회
    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from .models import Article
    from .serializers import ArticleSerializer
    
    @api_view(["GET", "POST"])
    def article_list(request):
        pass

    이렇게 method를 지정해 놓으면 다른 method가 요청되면 405(Method Not Allowed)가 return 됨.

     

    () 안에 아무것도 적지 않으면 기본적으로 GET만 허용.

    DFR의 함수형 뷰의 경우 필수적으로 작성 필요. rest_framework 패키지의 decorators.py 내에서 확인할 수 있다.

     

     

     

     

    urls.py

    # article 상세 조회
    from django.urls import path
    from . import views
    
    app_name = "articles"
    urlpatterns = [
        path("", views.article_list, name="article_list"),
        path("<int:pk>/", views.article_detail, name="article_list"),
    ]

     

    API는 redirect를 하는 일이 많이 없어서 name으로 참조할 일이 거의 없기 때문에 대체로 name을 써주지 않아도 된다. app들도 겹치거나 하는 일이 거의 없으므로 app_name을 써주지 않아도 된다. ↴

    urlpatterns = [
        path("", views.article_list),
        path("<int:pk>/", views.article_detail),
    ]

     

     

     

    views.py

    # article 상세 조회
    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from django.shortcuts import get_object_or_404
    from .models import Article
    from .serializers import ArticleSerializer
    
    ...
    
    @api_view(["GET"])
    def article_detail(request, pk):
        article = get_object_or_404(Article, pk=pk)
        serializer = ArticleSerializer(article)
        return Response(serializer.data)

     

    get_object_or_404를 써줘야 유효하지 않은 pk값이 들어오는 경우에 500번대 에러(서버문제)로 뜨지 않고 404(client의 잘못된 요청) 에러가 뜬다. 

     

     

     

    Create - JSON으로 주고받는다(Form도 가능함)

     

    article_list에 GET을 요청하면 목록 조회가 되고 POST를 하면 생성이 된다.

    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from django.shortcuts import get_object_or_404
    from .models import Article
    from .serializers import ArticleSerializer
    
    
    @api_view(["GET", "POST"])
    def article_list(request):
        if request.method == "GET":
            articles = Article.objects.all()
            serializer = ArticleSerializer(articles, many=True)
            return Response(serializer.data)
        
        elif request.method == "POST": # article 생성
            data = request.data
            title = data.get("title")
            content = data.get("content")
            article = Article.objects.create(title=title, content=content)
            return Response()

     

    위의 elif문처럼 model의 field명을 key값으로 사용할 수도 있지만 serializer를 이용하면 로직을 간편하게 처리할 수 있다. ↴

    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from django.shortcuts import get_object_or_404
    from .models import Article
    from .serializers import ArticleSerializer
    
    
    @api_view(["GET", "POST"])
    def article_list(request):
        if request.method == "GET":
            articles = Article.objects.all()
            serializer = ArticleSerializer(articles, many=True)
            return Response(serializer.data)
        
        elif request.method == "POST":
            serializer = ArticleSerializer(data=request.data)
            if serializer.is_valid():
                serializer.save() #생성
                return Response(serializer.data, status=201) #생략하면 status 200(ok). 써주면 201(created)
            return Response(serializer.errors, status=400) #valid하지 않은 경우, 400(Bad request)

    *serializer.errors를 써주면 오류메시지를 자동으로 보여줌. 원하는 오류메시지가 따로 있다면 serializer.errors 대신 {"message": "커스텀 에러메시지"}를 써주면 됨.

     

    is_valid() raise_exception=True를 넣어주면 맨 아랫줄 return Response(serializer.errors, status=400)을 안 써도 됨.

    elif request.method == "POST":
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save() #생성
            return Response(serializer.data, status=201)
        # return Response(serializer.errors, status=400)

     

     

    rest_framework 내에 status가 들어가 있어서 이걸 import 하면 정의되어 있는 상태코드를 갖다 쓸 수 있음. 직접 status code를 쓰는 것보다 정의되어 있는 것을 가져다 쓰는 것을 권장.(실수 방지)

    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from rest_framework import status
    from django.shortcuts import get_object_or_404
    from .models import Article
    from .serializers import ArticleSerializer
    
    
    elif request.method == "POST":
        serializer = ArticleSerializer(data=request.data)
        if serializer.is_valid(raise_exception=True):
            serializer.save() #생성
            return Response(serializer.data, status=status.HTTP_201_CREATED)

     

     

    post 요청 정상 처리
    post 요청 오류

     

     

     

     

    삭제는 article_detail에 DELETE 요청을 하면 삭제가 되도록 만들면 된다.

    GET요청만 받던 article_detail 코드를 고쳐서 DELETE요청을 받는 경우를 처리해 준다.

    elif request.method == "DELETE":
        article = get_object_or_404(Article, pk=pk)
        article.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

     

     

    수정은 article_detail에 PUT 요청을 하면 수정되도록 만들면 된다.

    elif request.method == "PUT":
        article = get_object_or_404(Article, pk=pk)
        serializer = ArticleSerializer(article, data=request.data, partial=True) 
        # partial=True를 넣어주면 부분 수정이 가능해짐
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data)

     

     

     

    CBV

    Django는 두 가지 방식으로 view를 작성할 수 있고 마찬가지고 DRF도 이 두가지 방식의 뷰 형태로 작성이 가능하다. 지금까지는 DRF에서 함수형(Function Based) View만 작성해 봤다. 이번에는 클래스형(Class Based) View를 써보자.

     

    Class Based View의 특징

    - 특정 Http Method의 처리를 함수로 분리할 수 있다.

    - 클래스를 사용하기 때문에 코드의 재사용성과 유지보수성이 향상된다.

    - 기본 APIView 외에도 여러 편의를 제공하는 다양한 내장 CBV가 존재한다.

     

    Class Based View의 종류

    - APIView: DRF CBV의 베이스 클래스 

    - Generic APIView: 일반적인 API작성을 위한 기능이 포함된 클래스. 보통 CRUD 기능이 대부분인 상황을 위해 여러 기능이 미리 내장되어 있다. APIView보다 코드를 더 적게 적어도 자동으로 만들어진다.

    - Mixin: 재사용 가능한 여러 가지 기능을 담고 있는 클래스. 여러 클래스를 섞어서 사용할 수 있도록 해준다.

    ListModelMixin: 리스트 반환 API를 만들기 위해 상속받는 클래스

    CreateModelMisxin: 새로운 객체를 생성하는 API를 만들기 위해 상속 받는 클래스

    - ViewSets: 여러 endpoint를 한 번에 관리할 수 있는 클래스. RESTful API에서 반복되는 구조를 더 편리하게 작성할 수 있는 방법을 제공.

     

     

     

    views.py

    from rest_framework.decorators import api_view
    from rest_framework.response import Response
    from rest_framework import status
    from rest_framework.views import APIView
    from django.shortcuts import get_object_or_404
    from .models import Article
    from .serializers import ArticleSerializer
    
    
    @api_view(["GET", "PUT", "DELETE"])
    def article_detail(request, pk):  # 상속
        pass
    
    
    class ArticleListAPIView(APIView):
        def get(self, request):
            articles = Article.objects.all()
            serializer = ArticleSerializer(articles, many=True)
            return Response(serializer.data)
    
        def post(self, request):
            serializer = ArticleSerializer(data=request.data)
            if serializer.is_valid(raise_exception=True):
                serializer.save()
                return Response(serializer.data, status=status.HTTP_201_CREATED)
    
    
    class ArticleDetailAPIView(APIView):
        def get(self, request, pk):
            article = get_object_or_404(Article, pk=pk)
            serializer = ArticleSerializer(article)
            return Response(serializer.data)
        def put(self, request, pk):
            article = get_object_or_404(Article, pk=pk)
            serializer = ArticleSerializer(article, data=request.data, partial=True)
            if serializer.is_valid(raise_exception=True):
                serializer.save()
                return Response(serializer.data)
        def delete(self, request, pk):
            article = get_object_or_404(Article, pk=pk)
            article.delete()
            return Response(status=status.HTTP_204_NO_CONTENT)

     

    urls.py

    from django.urls import path
    from . import views
    
    urlpatterns = [
        path("", views.ArticleListAPIView.as_view(), name="article_list"),
        path("<int:pk>/", views.ArticleDetailAPIView.as_view(), name="article_detail"),
    ]

     

    *** as_view() 메서드를 사용해서 url패턴에 연결해야 함.