django rest framework
- Views : Class-based Views(CBV) / Function Based Views(FBV)
- Generic views : Mixins / generics APIView
- Viewsets
@api_view, APIView -> Generic views -> viewsets 으로 갈수록 코드를 간략하게 API로 만들 수 있다.
APIView 클래스와 api_view 장식자
APIView 와 api_view 는 각각 CBV(클래스기반뷰)와 FBV(함수기반뷰)에 대응
두 가지 모두 뷰에 여러가지 기본 설정을 부여하게 됩니다. 이는 아래와 같고 상황에 맞춰서 이를 커스튬하여 사용하게 됩니다.
- 직렬화 클래스 지정
- renderer_classes
- default
- JSON 직렬화 : rest_framework.renderers.JSONRenderer
- HTML 페이지 직렬화 : rest_framework.renderers.TemplateHTMLRenderer
- 비직렬화 클래스 지정
- parser_classes
- default
- JSON 포맷 처리 : rest_framework.parsers.JSONParser
- FormParser : rest_framework.parsers.FormParser
- MultiPartParser : rest_framework.parsers.MultiPartParser
- 인증 클래스 지정
- authentication_classes
- default
- 세션기반인증 : rest_framework.authentication.SessionAuthentication
- HTTP basic 인증 : rest_framework.authentication.BasicAuthentication
- 사용량 제한 클래스 지정
- 권한 클래스 지정
- permission_classes
- default
- 누구라도 접근 허용 : rest_framework.permissions.AllowAny
- 요청에 따라 적절한 직렬화/비직렬화 선택
- content_negotiation_class
- 같은 URL 요청에 대해서 JSON 응답을 할 지, HTML 응답을 할 지 판단
- default
- rest_framework.negotiation.DefaultContentNegotiation
- 요청 내역에서 API 버전 정보를 탐지할 클래스 지정
- versioning_class
- 요청 URL의 HEADER에서 버전 정보를 탐지하여 맞는 버전을 호출
- default
APIView
우선 APIView 부터 자세히 알아보도록 합시다.
- 위에서 말했듯이 이는 CBV 중 하나이기 때문에 하나의 URL 에 대해서만 처리를 할 수 있습니다.
- /post/ 에 대한 CBV
- get : 포스팅 목록
- post : 새 포스팅 생성
- /post/int:pk/ 에 대한 CBV
- get : pk 번 포스팅 내용
- put : pk번 포스팅 수정
- delete : pk번 포스팅 삭제
- 요청 method 에 맞게 맴버함수를 정의하면 해당 method 로 request가 들어올 때 호출되게 됩니다.
- def get(self, request): pass def post(self, request): pass def put(self, request): pass def delete(self, request): pass
- 각 method 가 호출되면 위에서 봤던 설정에 맞춰 처리가 이루어집니다.
- 직렬화/비직렬화
- 인증 체크
- 사용량 제한 체크
- 권한 체크
- 요청한 버전 체크
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import authentication, permissions
from django.contrib.auth.models import User
class ListUsers(APIView):
"""
View to list all users in the system.
* Requires token authentication.
* Only admin users are able to access this view.
"""
authentication_classes = [authentication.TokenAuthentication]
permission_classes = [permissions.IsAdminUser]
def get(self, request, format=None):
"""
Return a list of all users.
"""
usernames = [user.username for user in User.objects.all()]
return Response(usernames)
# models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
create_at = models.DateTimeField(auto_now_add=True)
update_at = models.DateTimeField(auto_now=True)
# serializers.py
from rest_framework.serializers import ModelSerializer
from .models import Post
class PostSerializer(ModelSerializer):
class Meta:
model = Post
fields = '__all__'
# views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Post
from .serializers import PostSerializer
# 포스팅 목록 및 새 포스팅 작성
class PostListAPIView(APIView):
def get(self, request):
serializer = PostSerializer(Post.objects.all(), many=True)
return Response(serializer.data)
def post(self, request):
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=201)
return Response(serializer.errors, status=400)
from django.shortcuts import get_object_or_404
# 포스팅 내용, 수정, 삭제
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)
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_object(pk)
post.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
# urls.py
from django.urls import path, include
from . import views
urlpatterns = [
# FBV
path('post/', views.PostListAPIView.as_view()),
path('post/<int:pk>/',views.PostDetailAPIView.as_view()),
]
@api_view 장식자
api_view는 FBV 에 대해서 사용하는 장식자
# views.py
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Post
from .serializers import PostSerializer
from rest_framework.decorators import api_view
@api_view(['GET','POST'])
def post_list(request):
if request.method == 'GET':
qs = Post.objects.all()
serializer = PostSerializer(qs, many=True)
return Response(serializer.data)
else:
serializer = PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=201)
return Response(serializer.errors, status=400)
@api_view(['GET','PUT','DELETE'])
def post_detail(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == 'GET':
serializer = PostSerializer(post)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = PostSerializer(post, data=reqeust.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
post.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from news.models import Article
from news.api.serializers import ArticleSerializer
@api_view(['GET', 'POST'])
def article_list_create_api_view(request):
'''
코드 중략
'''
@api_view(['GET', 'PUT', 'DELETE'])
def article_detail_api_view(request, pk):
# try, except 대신 get_object_or_404를 import 해서 쓸 수도 있다.
try:
# pk(인스턴스의 id)값을 받아 어떤 인스턴스인지 특정
# url slug로 pk값을 받도록 urls.py에서 설정해준다.
article = Article.objects.get(pk = pk)
# 받은 pk값으로 조회했을 때 해당하는 인스턴스가 없다면 출력할 에러 코드와 메시지를 설정한다.
except Article.DoesNotExist:
return Response({'error' : {
'code' : 404,
'message' : "Article not found!"
}}, status = status.HTTP_404_NOT_FOUND)
# 만약 article이 존재한다면,
if request.method == 'GET':
serializer = ArticleSerializer(article)
return Response(serializer.data)
elif request.method == 'PUT':
serializer = ArticleSerializer(article, data = request.data)
# request에서 data를 받았으니 .is_valid() 필수
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status = status.HTTP_400_BAD_REQUEST)
elif request.method == 'DELETE':
article.delete()
# 인스턴스를 삭제한 뒤에는 204 NO CONTENT를 리턴
return Response(status = status.HTTP_204_NO_CONTENT)
# urls.py
from django.urls import path, include
from . import views
urlpatterns = [ # FBV
path('cbv/post/', views.post_list),
path('cbv/post/<int:pk>/',views.post_detail),
]
generics APIView
- Basic settings
- 다음 속성들을 통해 View를 컨트롤함
- queryset : View에서 객체를 반환하는 데 사용해야 하는 쿼리셋. 반드시 1) queryset 속성을 설정하거나, 2) get_queryset() 메서드로 override해서 사용해야 함.
- serializer_class : 입력된 값을 validate하거나 deserialize하거나, 출력값을 serialize할 때 사용하는 serializer 클래스. 일반적으로 이 속성을 설정하거나 get_serializer_class()메소드로 override해서 사용해야 함
- lookup_field : 개별 모델 인스턴스의 object 조회를 수행 할 때 사용해야하는 모델 필드. 기본값은 'pk'임. 하이퍼링크 된 API에 custom 값을 사용해야 하는 경우 API views와 serializer 클래스가 lookup필드를 설정해야 함.
- Pagination
- 다음 속성을 통해 리스트뷰에서 페이지네이션을 컨트롤하게 된다.
- pagination_class : 리스트 결과들을 페이지네이션할 때 사용되는 클래스임. 디폴트는 DEFAULT_PAGINATION_CLASS (rest_framework.pagination.PageNumberPagination 모듈안에) 세팅으로 결정됨.
- Filtering
- filter_backends : A list of filter backend classes that should be used for filtering the queryset. Defaults to the same value as the DEFAULT_FILTER_BACKENDS setting.
Mixins 상속
APIView 는 위에서 봤듯이 각 request method 마다 직접 serializer 처리를 해주었습니다.
하지만 이러한 부분들은 많이 사용되므로 여러 serializer 에 대해서 중복이 발생합니다.
따라서 rest_framework.mixins 에서는 이러한 기능들이 미리 구현이 되어 있습니다.
단, Mixin 클래스에 존재하는 메소드나 속성을 상속받는 클래스에서 사용할 경우 믹스인 클래스의 메소드가 오버라이딩되어 의도하지 않게 작동할 수 있으니 주의
- CreateModelMixin
- 모델 인스턴스를 생성하고 저장하는 역할을 하는 믹스인
- .create(request, *args, **kwargs) 메소드로 호출하여 사용
- 성공 시, 201 Created 리턴
- 실패 시, 400 Bad Request 리턴
- ListModelMixin
- Queryset을 리스팅하는 믹스인
- .list(request, *args, **kwargs) 메소드로 호출하여 사용
- GenericAPIView의 self.filter_queryset, self.get_queryset, self.get_serializer 등의 메소드를 활용해 데이터베이스에 저장되어 있는 데이터들을 목록 형태로 response body로 리턴
- 성공 시, 200 OK response 리턴
- RetrieveModelMixin
- 존재하는 모델 인스턴스를 리턴해 주는 믹스인
- .retrieve(request, *args, **kwargs) 메소드로 호출하여 사용
- 성공 시, 200 OK response 리턴
- 실패 시, 404 Not Found 리턴
- UpdateModelMixin
- 모델 인스턴스를 수정하여 저장해 주는 믹스인
- .update(request, *args, **kwargs) 메소드로 호출하여 사용
- 부분만 변경하고자 할 경우, .partial_update(request, *args, **kwargs)메소드를 호출하여야 하며, 이 때 요청은 HTTP PATCH requests여야 함
- 성공 시, 200 OK response 리턴
- 실패 시, 404 Not Found 리턴
- DestroyModelMixin
- 모델 인스턴스를 삭제하는 믹스인
- .destroy(request, *args, **kwargs) 메소드로 호출하여 사용
- 성공 시, 204 No Content 리턴
- 실패 시, 404 Not Found 리턴
queryset 과 serializer_class 를 지정해주기만 하면 나머지는 상속받은 Mixin 과 연결해주기만 하면 됩니다.
# views.py
from rest_framework.response import Response
from rest_framework import generics
from rest_framework import mixins
from .models import Post
from .serializers import PostSerializer
class PostListMixins(mixins.ListModelMixin, mixins.CreateModelMixin,generics.GenericAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
def get(self, request, *args, **kwargs):
return self.list(request)
def post(self, request, *args, **kwargs):
return self.create(request)
class PostDetailMixins(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.delete(request, *args, **kwargs)
# urls.py
from django.urls import path, include
from . import views
urlpatterns = [
# Mixin
path('mixin/post/', views.PostListMixins.as_view()),
path('mixin/post/<int:pk>/', views.PostDetailMixins.as_view()),
]
Concrete View Classes
Mixin 을 상속함으로서 반복되는 내용을 많이 줄일 수 있었습니다. 하지만 여러 개를 상속해야 하다보니 가독성이 떨어집니다. 다행히도 rest_framework 에서는 저들을 상속한 새로운 클래스를 정의해놨습니다.
총 9개의 클래스로 다음과 같습니다.
- generics.CreateAPIView : 생성
- generics.ListAPIView : 목록
- generics.RetrieveAPIView : 조회
- generics.DestroyAPIView : 삭제
- generics.UpdateAPIView : 수정
- generics.RetrieveUpdateAPIView : 조회/수정
- generics.RetrieveDestroyAPIView : 조회/삭제
- generics.ListCreateAPIView : 목록/생성
- generics.RetrieveUpdateDestroyAPIView : 조회/수정/삭제
# rest_framework/generics.py
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
# views.py
from rest_framework import generics
from .models import Post
from .serializers import PostSerializer
class PostListGenericAPIView(generics.ListCreateAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
class PostDetailGenericAPIView(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
# urls.py
from django.urls import path, include
from . import views
urlpatterns = [
# Mixin
path('mixin/post/', views.PostListGenericAPIView.as_view()),
path('mixin/post/<int:pk>/', views.PostDetailGenericAPIView.as_view()),
]
ViewSet
ViewSet 은 CBV 가 아닌 헬퍼클래스로 두 가지 종류가 있습니다.
- viewsets.ReadOnlyModelViewSet : 목록 조회, 특정 레코드 조회
- viewsets.ModelViewSet : 목록 조회, 특정 레코드 생성/조회/수정/삭제
- 실습
# views.py
# post 모델
from .models import Post
from .serializers import PostSerializer
from rest_framework import viewsets
class PostViewSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
Router 를 통해서 하나의 url 로 처리가 가능
#. urls.py
from django.urls import path, include
from . import views
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register('viewset',views.PostViewSet)
urlpatterns = [
path('',include(router.urls)),
]