📌 이 포스팅에서는 DRF의 APIView를 활용하여 CRUD 기능의 View를 구현해보았습니다.
🔥 APIView로 ListView 만들기
🔥 APIView를 활용하여 CRUD 구현
✔️ filter 기능을 사용하기 위해 is_public 필드를 추가하였다.
# models.py from django.conf import settings from django.db import models class Post(TimeStamp): author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) message = models.TextField() is_public = models.BooleanField(default=False, db_index=True) created_at = models.DateTimeField(auto_now_add=True) # 👈 추가 updated_at = models.DateTimeField(auto_now=True)
✔️ 마이그레이션 후, PostSerializer에도 추가된 "is_public" 필드를 fields에 추가한다.
# serializers.py from rest_framework import serializers from .models import Post # ostSerializer class PostSerializer(serializers.ModelSerializer): username = serializers.ReadOnlyField(source='author.username') class Meta: model = Post fields = [ 'pk', 'username', 'message', 'is_public', # 👈 추가 'created_at', 'updated_at', ]
✔️ 게시글 목록을 제공하는 View를 API를 아래처럼 만들 수 있다. APIView는 기본으로 제공하는 뷰로 View를 이미 상속받았다.
✔️ 참고로, APIView는 csrf_exempt 데코레이션으로 이미 감싸져있기 때문에 POST 요청에서 csrf token 체크를 하지 않는다.
✔️ APIView는 rest_framework에 View에 있고, 응답을 위해 Response를 import하여 사용할 수 있다.
✔️ Serializer는 첫번째 인자로 instance를 주고, 다수(Qeuryset)일 경우 many=True 옵션을 준다.
✔️ serializer로 GET 매서드를 사용할 때는, 첫번째 인자에 instance 또는 Queryset를 주어야 한다.
✔️ serializer한 data에 .data를 붙이면, 직렬화된 데이터를 쉽게 반환받을 수 있다.
from rest_framework.views import APIView from rest_framework.response import Response from .serializers import PostSerializer from .models import Post # PublicPostListAPIView class PublicPostListAPIView(APIView): def get(self, request): qs = Post.objects.filter(is_public=True) serializer = PostSerializer(qs, many=True) # 👈 다수의 객체들을 이용할 때는 many=True 옵션을 지정 return Response(serializer.data) public_post_list = PublicPostListAPIView.as_view()
✔️ urls.py는 아래와 같이 매핑해준다.
from django.urls import include, path from rest_framework.routers import DefaultRouter from . import views router = DefaultRouter() router.register('post', views.PostViewSet) urlpatterns = [ path('public/', views.public_post_list), # 👈 localhost:8000/public path('', include(router.urls)), ]
✔️ FBV로 구현할 때는 framework.decorators에 "api_view"를 import 해준다.
✔️ "api_view"를 함수에 데코레이션으로 달고, 그 안에 HTTP 매서드를 매핑해준다.
from rest_framework.decorators import api_view from rest_framework.response import Response from .serializers import PostSerializer from .models import Post # public_post_list @api_view(['GET']) # 👈 GET 매서드 지정 def public_post_list(request): qs = Post.objects.filter(is_public=True) serializer = PostSerializer(qs, many=True) return Response(serializer.data)
✔️ APIView를 통해 CBV, FBV를 구현했던 것을 generic.ListAPIView를 이용하여 더 손쉽게 구현할 수 있다. 단, generic에서 제공하는 View로 하나의 Class에서 하나의 URL과 매핑된다.
✔️ 즉, APIView는 가장 기본이 되는 View이며, 이를 표준화한 것이 generic에서 제공하는 View이다. 이를 더 표준화하고 통합해서 여러 URL을 하나의 Class로 처리할 수 있는 것이 ViewSet이라고 생각하면 된다.
✔️ ListAPIView는 rest_framework.generics를 import하여 사용한다.
✔️ queryset와 serializer_class를 지정해주면, 위의 기능과 같이 작동한다.
from rest_framework import generics from rest_framework.response import Response from .serializers import PostSerializer from .models import Post # PublicPostListAPIView class PublicPostListAPIView(generics.ListAPIView): queryset = Post.objects.filter(is_public=True) serializer_class = PostSerializer public_post_list = PublicPostListAPIView.as_view()
✔️ PostListAPIView는 GET 요청으로 공개 게시글에 대한 list를 제공하거나, 게시글을 생성할 수 있는 API이다.
✔️ save() 매서드를 호출할 때, 현재 작성자를 참조할 수 있도록, 인자로 넘겨줘야하는데 이 때 model에서 사용하는 field명을 지정해야한다. ReadOnly로 지정할 필드명을 상용할 경우 Error가 발생한다.
from rest_framework.views import APIView from rest_framework.response import Response from .serializers import PostSerializer from .models import Post class PostListAPIView(APIView): # public post list 요청 def get(self, request): qs = Post.objects.filter(is_public=True) serializer = PostSerializer(qs, many=True) return Response(serializer.data) # post 작성 def post(self, request): serializer = PostSerializer(data=request.data) if serializer.is_valid(): serializer.save(author=request.user) # 👈 save 매서드에 인자로 전달 return Response(serializer.data, status=201) return Response(serializer.errors, status=400) public_post_list = PostListAPIView.as_view()
✔️ PostDetailAPIView는 pk(id)를 기준으로 읽기, 수정, 삭제 기능을 구현할 수 있다.
✔️ urls.py의 경로는 아래와 같이 pk값을 읽어올 수 있도록 명시한다.
from django.urls import include, path from rest_framework.routers import DefaultRouter from . import views router = DefaultRouter() router.register('post', views.PostViewSet) urlpatterns = [ path('public/', views.public_post_list), path('public/<int:pk>/', views.public_post_detail), # 👈 추가 path('', include(router.urls)), ]
✔️ 해당 pk에 대한 객체를 가져오기 위해 get_object_or_404를 import 한다.
✔️ 수정은 첫번째 인자로 기존 인스턴스를 넘겨주고, 두번째 인자로 수정할 데이터(request.data)를 전달한다.
from django.shortcuts import get_object_or_404 # 👈 추가 from rest_framework.views import APIView from rest_framework.response import Response from .serializers import PostSerializer from .models import Post # PostDetailAPIView class PostDetailAPIView(APIView): def get_object(self, pk): return get_object_or_404(Post, pk=pk) def get(self, request, pk, format=None): post = self.get_object(pk) serializer = PostSerializer(post) return Response(serializer.data) def put(self, request, pk): post = self.get_object(pk) serializer = PostSerializer(post, data=request.data) # 👈 instance, 수정 데이터 순 if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def delete(self, request, pk): post = self.get_objects(pk) post.delete() return Response(status=status.HTTP_204_NO_CONTENT) public_post_detail = PostDetailAPIView.as_view()