Django REST Framework

이태성·2020년 10월 18일

django

목록 보기
1/1

Django REST Framework

RESTful한 아키텍처 구현을 도와주는 프레임 워크이다. 기존의 장고를 사용할때 퓨어하게 사용해도 REST형식을 지키면서 개발이 가능하지만 개발자가 직접 코딩으로 써줘야 되는 부분이 많다. 이때 REST Framework를 사용하면 직접 커스텀하거나 코딩으로 써줘야 되는 부분에 대해서 많은 수고로움을 덜 수 있다. 즉, 보일러 플레이트가 많이 적다는 것이다. 실제 현업에서도 많이 사용되는 프레임워크이며 손에 익을 경우 빠르게 RESTful한 백앤드 서버를 만들 수 있다.

보일러 플레이트
정보통신 기술에서 변경없이 재사용 가능한 저작품등을 말한다. 보일러 플레이트 코드라고도 말하는데 재사용 가능한 프로그램을 말할때 부른다. 예시로 React에서는 cra(create-react-app)이라는 보일러 플레이트를 만들어서 초보자도 쉽게 만들 수 있게 했다.

REST Framework 장점

  1. Authentication 정책
    OAuth1, OAuth2를 지원한다. 이것을 통해 소셜로그인도 가능하며 로그인, 회원가입 기능을 간편하게 구현할 수 있다.
  2. Serializer
    Serializer를 통해 ORM 데이터이든 Non-ORM 데이터이든 지원이 가능하다.
  3. 커스텀 가능한 view
    기본적으로 제공되는 view로 왠만한 CRUD를 커버할 수 있지만 좀 더 최적한된 기능을 위한 커스텀도 가능하다.
  4. 광범위한 문서화와 커뮤니티
    Mozilla, Red Hat, Heroku, Eventbrite 등 국제적으로 인정받는 회사에서 사용 및 신뢰함.
    이렇게 Django REST Framework 공식문서에서 소개하고 있다.

REST Framework Quick Start

공식 홈페이지에 있는 Quick Start를 보면 REST Framework를 간단하게 전체적으로 확인 할 수 있다. Serializer와 Viewset, router를 사용하면서 빠르게 API하나를 만들어 내는 코스를 설명하고 있다.

Serializer

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를 상속받는다.
HyperlinkedModelSerializer
ModelSerializer는 Serializer를 상속받고
ModelSerializer
Serializer는 BaseSerializer를 상속받는다.
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를 지원하고 필요한 기능들에 대해서 거의 대부분을 지원하기 때문에 굳이 필요한 기능이 아니라면 구현할 필요가 없다.

NestedSerializer

또한 모델의 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": "제목"
}

형식으로 데이터가 나올 것이다.

Viewset

CBD(Class Based View)기준으로 설명
ModelViewSet은 mixin들과 GenrericViewSet을 상속받아 실행된다.
ModelViewSet
GenericViewSet은 ViewSetMixin과 GenericAPIView를 상속받는다.
GenericViewSet
ViewSet은 ViewSetMixin과 APIView를 상속받는다.
ViewSet
ViewSetMixin
GenericAPIView는 APIView를 상속받는다.
GenericAPIView
APIView는 기본 View를 상속받아 실행된다.
APIView
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)
Request

REST Framework는 기존의 JsonResponse를 사용할 필요가 없이 Response를 사용하여 클라이언트가 요청한 content-type에 따라 Json이 될 수도 있고 Xml이 될 수도 있는 다양한 형태를 유연하게 응답할 수 있다.
Response

단순하게 정수 형태로만 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의 언더바(_)는 하이픈(-) 으로 교체됩니다.
action

Router

기존의 path를 맵핑하는 방식에서 ViewSet을 사용하면 각각의 method에 맵핑을 해줘야 한다. 만약 커스텀을 한다면 커스텀한 함수이름을 직접 써줘야 한다.

먼저 DefaultRouter는 SimpeRouter를 상속받고
DefaultRouter
SimpleRouter는 BaseRouter를 상속받아 실행된다.
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뒤에 적어주면 된다.

0개의 댓글