RESTful한 아키텍처 구현을 도와주는 프레임 워크이다. 기존의 장고를 사용할때 퓨어하게 사용해도 REST형식을 지키면서 개발이 가능하지만 개발자가 직접 코딩으로 써줘야 되는 부분이 많다. 이때 REST Framework를 사용하면 직접 커스텀하거나 코딩으로 써줘야 되는 부분에 대해서 많은 수고로움을 덜 수 있다. 즉, 보일러 플레이트가 많이 적다는 것이다. 실제 현업에서도 많이 사용되는 프레임워크이며 손에 익을 경우 빠르게 RESTful한 백앤드 서버를 만들 수 있다.
보일러 플레이트
정보통신 기술에서 변경없이 재사용 가능한 저작품등을 말한다. 보일러 플레이트 코드라고도 말하는데 재사용 가능한 프로그램을 말할때 부른다. 예시로 React에서는 cra(create-react-app)이라는 보일러 플레이트를 만들어서 초보자도 쉽게 만들 수 있게 했다.
공식 홈페이지에 있는 Quick Start를 보면 REST Framework를 간단하게 전체적으로 확인 할 수 있다. Serializer와 Viewset, router를 사용하면서 빠르게 API하나를 만들어 내는 코스를 설명하고 있다.
from django.contrib.auth.models import User, Group
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ['url', 'username', 'email', 'groups']
class GroupSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Group
fields = ['url', 'name']
공식문서 Quick Start에서는 serializer를 HyperlinkedModleSerializer를 사용한다.
Serializer란
Json이나 Xml형식의 데이터를 파이썬 형식으로 바꾸거나 되돌릴때 사용한다. 즉, 파이썬 데이터를 Json이나 Xml형식으로 하려면 랜더링을하고 Json이나 Xml형식의 데이터를 파이썬 데이터로 바꾸려면 파서를 해야한다. 원래는 JsonResponse를 쓰는 형식으로 기존의 데이터를 바꿔야 되지만 REST Framework에서는 Serializer를 지원하여 간편하게 바꿀 수 있다.
먼저 HyperlinkedModelSerializer는 ModelSerializer를 상속받는다.

ModelSerializer는 Serializer를 상속받고

Serializer는 BaseSerializer를 상속받는다.


이렇게 기본 Serializer는 BaseSerializer를 상속받아서 생기는데 기본적인 Serializer만 가지고 만들게 되면
from rest_framework import serializers
from .models import Example
class ExampleSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=100)
is_deleted = serializers.BooleanField(default=False)
def create(self, validated_data):
return Example.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.title = validated_data.get('title', instance.titel)
return instance
이런 형식으로 기존 models의 값을 다시 serializer의 형식으로 받아야 되고 instance 생성 혹은 갱신마다 validate을 확인하는 방식으로 하는 코드를 짜야만 하기 때문에 Serializer보다는 ModelSerializer를 사용한다.
ModelsSerializer는
from rest_framework import serializers
from .models import Example
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = Example
fields = ('id','title') 혹은 '__all__'
exclude = ('is_deleted',)
Meta에 형식값으로 model과 fields를 넣어주면 아주 쉽게 Serializer를 구성할 수 있다. 여기서 exclude는 Json으로 랜더링 하지 않을 값을 지정해주면 된다. 즉, 클라이언트에게 보여주질 않을 데이터를 넣어주면 된다. ModelSerializer는 기본적으로 created, updated를 지원하고 필요한 기능들에 대해서 거의 대부분을 지원하기 때문에 굳이 필요한 기능이 아니라면 구현할 필요가 없다.

또한 모델의 1대다 관계가 설정되어있는 경우에 NestedSerializer를 사용하여 데이터를 보여줄 수 있다.
1대다 관계를 REST Framework에서 Reverse relation이라고 표현하며 1을 기준으로 작성한다.
class ExampleSerializer(serializers.ModelSerializer):
example_related = Example_realedSerializer(many=True, read_only=True)
class Meta:
model = Example
fields = ('id', 'title', 'example_related') 혹은 '__all__'
exclude = ('is_deleted',)
형식으로 관계를 설정해주면 된다. 여기서 1이 Example이고 다가 Example_related이다. 여기서 example_related를 그냥 쓰려면 model에서 related가 선언되어있어야 되고 만약 안되있다면 _set형식으로 사용가능하다. 이렇게 되면 Json 데이터가
"id": 1,
"title": "제목",
"example_related": {
"id": 1,
"title": "관계 제목",
}
형식으로 데이터가 나온다. 그리고 다를 중심으로도 Serializer를 만들 수도 있는데
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = Example
fields = ('id', 'title', 'example_related') 혹은 '__all__'
exclude = ('is_deleted',)
class Example_relatedSerializer(serializers.ModelsSerializer):
class Meta:
models = Example_realted
fields = '__all__'
exclude = ('is_deleted',)
def to_representation(self, instance):
response = super().to_representation(instance)
response['example'] ExampleSerializer(instance.example).data
return response
이렇게 다를 중심으로 데이터를 설정할 수도 있다. 이렇게 된다면
"id": 1,
"title": "관계제목",
"example" {
"id": 1,
"title": "제목"
}
형식으로 데이터가 나올 것이다.
CBD(Class Based View)기준으로 설명
ModelViewSet은 mixin들과 GenrericViewSet을 상속받아 실행된다.

GenericViewSet은 ViewSetMixin과 GenericAPIView를 상속받는다.

ViewSet은 ViewSetMixin과 APIView를 상속받는다.


GenericAPIView는 APIView를 상속받는다.

APIView는 기본 View를 상속받아 실행된다.

CreateModelMixin, ListModelMixin, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin
기본적으로 퓨어장고를 할때는 View를 상속받아서 views.py를 작성하지만 REST Framework에서는 APIView를 상속받아서 views.py를 작성한다. 기본적으로 가장 간단한 상태이므로 대부분의 코드를 써줘야 하며 차이점은 Serializer를 사용할 수 있기 때문에 데이터 베이스에서 가져온 query_set을 Serializer로 넘겨주고 그것을 return한다.
class ExampleList(APIView):
def get(self, request, format = None):
examples = Example.objects.all()
serializer = ExampleSerializer(examples, many=True)
return Response(serializer.data)
def post(self, request, format = None):
serializer = ExampleSerializer(request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status = status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
여기서 format=None은 URL과 관련있는데 만약 www.example.com/user/format=html과 같은 다양한 형식을 허용한 거라면 format parameter를 허용하면 되지만 그렇지 않을 거라면 넣어줘야만 한다.
REST Framework는 기존의 HTTP Request를 상속받는 Request를 사용한다. request.data로 사용하며 request.POST와 역활이 비슷하지만 더 넓은 범위의 파싱이 가능하고 더 많은 method를 사용할 수 있다.(POST, PUT, PATCH)
REST Framework는 기존의 JsonResponse를 사용할 필요가 없이 Response를 사용하여 클라이언트가 요청한 content-type에 따라 Json이 될 수도 있고 Xml이 될 수도 있는 다양한 형태를 유연하게 응답할 수 있다.
단순하게 정수 형태로만 status를 나타내면 숫자의 작은 차이를 놓칠 수도 있다. REST Framework는 status모듈에서 다양한 상태코드를 메세지와 함께 제공한다.
HTTP_100_CONTINUE = 100 HTTP_101_SWITCHING_PROTOCOLS = 101 HTTP_200_OK = 200 HTTP_201_CREATED = 201 HTTP_202_ACCEPTED = 202 HTTP_203_NON_AUTHORITATIVE_INFORMATION = 203 HTTP_204_NO_CONTENT = 204 HTTP_205_RESET_CONTENT = 205 HTTP_206_PARTIAL_CONTENT = 206 HTTP_207_MULTI_STATUS = 207 HTTP_208_ALREADY_REPORTED = 208 HTTP_226_IM_USED = 226 HTTP_300_MULTIPLE_CHOICES = 300 HTTP_301_MOVED_PERMANENTLY = 301 HTTP_302_FOUND = 302 HTTP_303_SEE_OTHER = 303 HTTP_304_NOT_MODIFIED = 304 HTTP_305_USE_PROXY = 305 HTTP_306_RESERVED = 306 HTTP_307_TEMPORARY_REDIRECT = 307 HTTP_308_PERMANENT_REDIRECT = 308 HTTP_400_BAD_REQUEST = 400 HTTP_401_UNAUTHORIZED = 401 HTTP_402_PAYMENT_REQUIRED = 402 HTTP_403_FORBIDDEN = 403 HTTP_404_NOT_FOUND = 404 HTTP_405_METHOD_NOT_ALLOWED = 405 HTTP_406_NOT_ACCEPTABLE = 406 HTTP_407_PROXY_AUTHENTICATION_REQUIRED = 407 HTTP_408_REQUEST_TIMEOUT = 408 HTTP_409_CONFLICT = 409 HTTP_410_GONE = 410 HTTP_411_LENGTH_REQUIRED = 411 HTTP_412_PRECONDITION_FAILED = 412 HTTP_413_REQUEST_ENTITY_TOO_LARGE = 413 HTTP_414_REQUEST_URI_TOO_LONG = 414 HTTP_415_UNSUPPORTED_MEDIA_TYPE = 415 HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE = 416 HTTP_417_EXPECTATION_FAILED = 417 HTTP_418_IM_A_TEAPOT = 418 HTTP_422_UNPROCESSABLE_ENTITY = 422 HTTP_423_LOCKED = 423 HTTP_424_FAILED_DEPENDENCY = 424 HTTP_426_UPGRADE_REQUIRED = 426 HTTP_428_PRECONDITION_REQUIRED = 428 HTTP_429_TOO_MANY_REQUESTS = 429 HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS = 451 HTTP_500_INTERNAL_SERVER_ERROR = 500 HTTP_501_NOT_IMPLEMENTED = 501 HTTP_502_BAD_GATEWAY = 502 HTTP_503_SERVICE_UNAVAILABLE = 503 HTTP_504_GATEWAY_TIMEOUT = 504 HTTP_505_HTTP_VERSION_NOT_SUPPORTED = 505 HTTP_506_VARIANT_ALSO_NEGOTIATES = 506 HTTP_507_INSUFFICIENT_STORAGE = 507 HTTP_508_LOOP_DETECTED = 508 HTTP_509_BANDWIDTH_LIMIT_EXCEEDED = 509 HTTP_510_NOT_EXTENDED = 510 HTTP_511_NETWORK_AUTHENTICATION_REQUIRED = 511
이번엔 Mixin을 사용해서 기본 APIView에서 보다 더 간결하게 표현할 수 있다. 각각의 method로 가기전에 class에 맞게 미리 queryset과 serializer를 하기때문에 메소드에서의 처리가 쉬어졌으며 각 메소드의 return 부분에서 self와 함께 상속받은 Mixin을 표시해주면서 간결하게 표현할 수 있게 되었다.
class ExampleList(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
GenericAPIView를 이용하여 더 간단하게 구현 할 수 있다.
class Examplelist(generics.ListCreateAPIView):
queryset = Exmaple.objects.all()
serializer_class = ExmapleSerializer
class ExampleDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
GenericAPIView로 만들 수 있는 class 목록
대부분이 Mixin을 상속받아 그것을 바탕으로 새로운 형태의 class를 만든다.
CreateAPIView(mixins.CreateModelMixin, GenericAPIView), ListAPIView(mixins.ListModelMixin, GenericAPIView),
RetrieveAPIView(mixins.RetrieveModelMixin, GenericAPIView),
DestroyAPIView(mixins.DestroyModelMixin, GenericAPIView),
UpdateAPIView(mixins.UpdateModelMixin, GenericAPIView),
ListCreateAPIView(mixins.ListModelMixin, mixins.CreateModelMixin, GenericAPIView),
RetrieveUpdateAPIView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, GenericAPIView),
RetrieveDestroyAPIView(mixins.RetrieveModelMixin, mixins.DestroyModelMixin, GenericAPIView),
RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, GenericAPIView)
하지만 GenericAPIView를 사용하더라도 최대 3가지 기능을 한 class에서 사용할 수 있다. 그래서 ModelsViewSet을 이용하면 CRUD+List까지 하나의 class에서 사용할 수 있어 보일러 플레이트에 좋다. 또한 URL을 설정할때 Router를 이용할 수 있기때문에 views.py뿐만 아니라 다른 영역의 코드 간결성과 가독성도 좋아진다.
class SnippetViewSet(viewsets.ModelViewSet):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
@action(detail = True, methond=['patch'])
def highlight(self,request,*args,**kwargs):
instance = self.get_object()
instance.is_deleted = True
instance.save()
serializer = self.get_serializer(instance)
return Response(serializer.data)
def perform_create(self, serializer):
serializer.save()
@action
ModelViewSet을 사용하더라도 특정한 상황에 맞게 커스텀해야될 필요가 생길 수 있다. 그때 action을 데코레이터로 사용하면 되는데 내가 원하는 method의 기능을 함수에 데코레이터 해주면 된다.
action 데코레이터는 detail과 method를 parameter로 넣어야 한다. method의 기본 값은 get이다.
detail=True
url : /prefix/{pk}/{function name}/
name : {model name}-{function name}
detail=False
url : /prefix/{function name}/
name : {model name}-{function name}
이 때 모든 이름은 소문자이며, function name의 언더바(_)는 하이픈(-) 으로 교체됩니다.
기존의 path를 맵핑하는 방식에서 ViewSet을 사용하면 각각의 method에 맵핑을 해줘야 한다. 만약 커스텀을 한다면 커스텀한 함수이름을 직접 써줘야 한다.
먼저 DefaultRouter는 SimpeRouter를 상속받고

SimpleRouter는 BaseRouter를 상속받아 실행된다.


기존의 방식은 하나의 함수를 path에 경로를 지정해준 다음 path에 맞게 클래스(함수)를 써주면서 지정해줘야 되지만 REST Framework는 format_suffix_patterns를 사용하여 각각의 method에 맞게 class를 맵핑해주면 된다.
example_list = ExampleViewSet.as_view({
'get': 'list',
'post': 'create',
'patch': 'highlight'
})
urlpatterns = format_suffix_patterns([
path('snippets/', snippet_list),
path('', views.api_root)
])
하지만 Router를 사용하면 모든 값을 직접 맵핑 해줄 필요없이 Router로 클래스를 등록만 하면 자동으로 path가 설정된다.
가장 기본인 DefaultRouter를 사용하면
router = DefaultRouter()
router.register(r'examples',views.ExampleViewSet)
urlpatterns = [
path('admin/', admin.site.urls),
]
urlpatterns += router.urls
이런식으로 설정 할 수 있다.
register는 두 가지 필수 인수가 있는데
1. prefix : router의 set에 사용할 URL접두어 입니다.
2. viewset : viewset클래스입니다.
ex)
URL pattern: ^users/$ Name: 'user-list'
URL pattern: ^users/{pk}/$ Name: 'user-detail'
URL pattern: ^accounts/$ Name: 'account-list'
URL pattern: ^accounts/{pk}/$ Name: 'account-detail'
위의 예시는 Router로 등록했을때 기본 basename이 user 와 account로 되어있는 경우이다. 보통의 경우에 basename이 자동으로 지정되지만 커스텀된 query_set을 쓸 경우는 basename을 직접 지정해줘야 error가 나지않고 router가 지정되 위치로 맵핑을 할 수 있다.
basename은 registe할때 router.register(r'examples',views.ExampleViewSet, 'Example')와 같이 prefix와 viewset 다음에 써주면 된다.
꼭 DefaultRouter를 사용할 필요는 없으며 다른 형식에 맞게 다양한 Router를 사용해도 된다.
모든 클래스를 register로 맵핑 할 필요는 없고 urlpatterns를 통해 맵핑하여도 된다.
router = routers.SimpleRouter()
router.register(r'users', UserViewSet)
router.register(r'accounts', AccountViewSet)
urlpatterns = [
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
]
urlpatterns += router.urls
이런 식으로 맵핑 할 경우 urlpatterns와 router를 더해줘야 된다.
기본적으로 SimpleRouter로 만든 URL 뒤에는 슬래시가 추가됩니다. 이 동작은 라우터를 인스턴스화 할때 trailing_slash 인수를 False로 설정하여 수정할 수 있습니다. 예: router = SimpleRouter(trailing_slash=False)
또한 각각의 viewset에서도 router로 데코레이팅 하면 rotuer를 통한 맵핑이 가능하다.
list router
'get': 'list'
'post': 'create'
detail router
'get': 'retrieve'
'put': 'update'
'patch': 'partial_update'
'delete': 'destroy'
class ExampleViewSet(ModelViewSet):
...
@detail_route(methods=['post'])
def set_password(self, request, pk=None):
URL pattern: ^examples/{pk}/set_password/$ Name: 'user-set-password'
와 같이 router에 맵핑이 생긴다.
또한 custom된 viewset에서 detail_router를 사용해서 url_path를 직접 지정해줄 수 있다.
url_path='change-password'와 같은 형식을 methods뒤에 적어주면 된다.