당신은 멋쟁이, 우리는 장고쟁이~

0%

간단한 TodoList API 로 만들기


Django API RESTFRAMEWORK 학습을 진행 하면서 RESTFRAMEWORK 문서에 나와 있는 튜토리얼을 하나하나 따라서 진행 하며 학습을 해보았지만.

컨텐츠는 좋게 짜여진 튜토리얼이지만, 무언가 머리에 남는게 별로 없었다. 사실 처음 접했을때는 너무 어려웠었다. 따라서, 조금 쉬운 예제를 가지고 API 개발을 진행하면서 학습을 하기로 하였고. 여러모로 접근도 쉽고, 간편하게 짤수 있는 투두리스트 사이트를 API로 개발해 보면서, 학습을 진행해 보았다.


1. project 생성 및 앱 등록

가상환경과 Django 가 모두 세팅되었다고 가정하고.

projects/todo 폴더를 만들고 이동후에, 아래 커맨드들을 통해 Django 프로젝트를 생성해 주었다.


1
2
3
4
5
6
7
8
9
10
11
12
# todo 라는 django project 생성
django-admin startproject todo .

# 폴더 구조
~/projects/todo  ls
manage.py todo

# todolist 라는 앱 생성
python manage.py startapp todolist

~/projects/todo  ls
manage.py todo todolist # todolist 폴더가 생겨난다

제대로 프로젝트가 생성이 되었고, 앱도 생성이 되었다. 다른것을 하기전에, 생성된 앱을 todo/settings.pyINSTALLED_APPS 에 추가해주었다.


1
2
3
4
5
6
7
8
9
10
11

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',

'todolist', # todolist 앱 추가
]

더 읽어보기 »

Fundamental Terms


Django API 공부를 시작하기 전에, 알아야 할 기본적인 terminology 를 정리해 보았다.

API 공부를 시작하기전에는 잘 몰랐던 단어들과 컨셉을 다시 정리해 본다.


1. HTTP VERBS


HTTP 프로토콜은 요청 방법 이라는것이 존재한다. 각 요청은 request method 라는 요청 방법을 가지고 있어서, 요청 방법에 따라 CRUD 연산이 가능하다.

CRUD 는 웹개발에서 너무 기본적인 텀이지만, Create, Read, Update, Delete 의 앞 글자들을 따서 만들어진 단어라고 볼수 있다.


가장 흔한 4개의 요청 방법은, 아래와 같다.


  1. POST
  2. GET
  3. PUT
  4. DELETE

이 요청 방법들을 HTTP Verbs 라고 부르는데, 영어 단어 verbs 는 동사라는 뜻을 가지고 있고. HTTP 가 가진 동사 (포스트 하다, 겟 하다, 풋 하다, 딜리트 하다, ~~하다 라는 동사) 라고 해서 HTTP Verbs 라고 불리우는것 같다.


보통 이 동사들은, 각각의 CRUD 동작과 매칭이 되는데. 각 동작들이 어떤 HTTP VERB 들과 매칭이 되는지 테이블로 정리해 보았다.



CRUD (Create, Read, Update, Delete) HTTP Verbs
Create (생성) POST
Read (읽기) GET
Update (갱신) PUT
Delete (삭제)

2. Endpoints


REST FRAMEWORK 튜토리얼을 따라했었을때, 몰랐던 단어가 바로 이 endpoints 였다.


Endpoints 가 무엇인지 이해하려면, URL 부터 알아야 한다.


거의 모든 웹페이지는 HTML, CSS, JavaScript 혹은 다른 element 와 함께 구성이 되어 있고. 해당 웹페이지를 위한 URL 이 존재한다.

예를들면, mysite.com.au/page/1 이라는 URL 주소가 존재할때, 해당 URL 을 브라우저 주소창에 입력하면 웹페이지가 화면에 출력이 된다.


더 읽어보기 »

Trade-offs between views vs viewsets


viewsets 를 사용하는것은 매우 유용할수 있습니다.


viewsets 는 여러분의 API 에 결쳐있는 URL 이름들이 일관성을 가지게 해주고, 작성해야 하는 코드를 최소화 해줍니다.


그리고 작동과 표시를 해주는것을 좀 더 단단하게 해줍니다.


하지만, 이는 언제나 맞는 접근방식이라는 뜻은 아닙니다. 클래스기반 뷰를 사용하는것과 함수 기반 뷰를 사용할때의 장단점을 비교해보는것과 비슷합니다.


viewsets 를 사용하는것으로, 개별적으로 views 를 만드는것보다 덜 명시적입니다.


Using Routers


우리는 View 클래스들 대신에 ViewSet 클래스들을 사용하고 있기 때문에, 우리는 사실 우리 스스로 URL Conf 를 딪인해줄 필요가 없습니다.


관례적으로, 리소스들을 views 와 urls 에 묶어주는것은 Router 를 사용함으로써 자동으로 다루어 지게 할수 있습니다. 우리가 해야할것은, 적절한 뷰 세트들을 router 에 등록해 주는것입니다.


snippets/urls.py 파일을 아래와 같이 수정해 줍시다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.urls import path, include 
from rest_framework.routers import DefaultRouter
from snippets import views

# create a router and register our viewsets with it

router = DefaultRouter()
router.register(r'snippets', views.SnippetViewSet)
router.register(r'users', views.UserViewSet)

# The API URLs are now determined automatically by the router

urlpatterns = [
path('', include(router.urls)),
]

뷰셋들을 라우터와 등록해주는것은 urlpattern 을 제공해주는것과 비슷합니다.


우리는 두개의 인자를 포함해 줍니다 - 뷰를 위한 URL prefix 와 뷰셋 그자체.


우리가 사용하는 DefaultRour 클래스는 자동으로 API root 뷰를 생성해줍니다. 따라서, 우리는 api_root 메서드를 우리 view 모듈에서 지워줄수 있습니다.


Binding ViewSets to URLs explicitly


handler 메서드들은 URL Conf 를 정의할때에만 동작들에 묶여집니다.


무슨일이 일어나는지 보기 위해, 명시적으로 ViewSets 로 부터, View 의 세트를 생성해 줍니다.


snippets/urls.py 파일안에, 우리의 ViewSet 클래스들을 묶어줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from snippets.views import SnippetViewSet, UserViewSet, api_root 
from rest_framework import renderers

snippet_list = SnippetViewSet.as_view({
'get': 'list',
'post': 'create'
})
snippet_detail = SnippetViewSet.as_view({
'get':' retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
})
snippet_highlight = SnippetViewSet.as_view({
'get': 'highlight'
}, renderer_classes=[renderers.StaticHTMLRenderer])

user_list = UserViewSet.as_view({
'get': 'list',
})

user_detail = UserViewSet.as_view({
'get': 'retrieve'
})

우리가 어떻게 다수의 뷰들을 각 ViewSet 클래스로부터 생성하고 있는지 알아야 합니다.


http메서드들을 각 뷰에 요구되는 엑션들을 묶어주는식으로 이 작업을 하고 있다는것을 인지하고 있어야 합니다.


이제 우리는 우리의 리소스들을 뷰들에 묶어주었습니다. 우리는 이전처럼, 뷰들을 URL Conf 와 함께 등록 해줄수 있습니다.


1
2
3
4
5
6
7
8
urlpatterns = format_suffix_patterns([
path('', api_root),
path('snippets/', snippet_list, name='snippet_list'),
path('snippets/<int:pk>/', snippet_detail, name='snippet-detail'),
path('snippets/<int:pk>/higlight', snippet_highlight, name='snippet-highlight'),
path('users/', user_list, name='user-list'),
path('users/<int:pk>/', user_detail, name='user-detail')
])

Refactoring to use ViewSets


현재 views 를 viewsets 를 사용하기 위해서, 코드를 다시 refactor 해봅시다.


우선, 우리의 UserList 와 UserDetail 뷰들을 하나의 UserViewSet 으로 리펙터 해줍니다. 우리는 이 두개의 뷰들을 삭제하고, 하나의 클래스로 대체할수 있습니다.


1
2
3
4
5
6
7
8
from rest_framework import viewsets 

class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically provides 'list' and 'detail' actions
"""
queryset = User.objects.all()
serailizer_class = UserSerializer

우리는 여기에 ReadOnlyModelViewSet 클래스를 사용하여, 자동으로 기본동작인 read-only 를 주었습니다.


우리는 여전히 queryset 과 serializer_class 속성들을 우리가 일반 뷰들을 사용할때처럼 정확하게 명시해 주었습니다. 하지만, 우리는 더이상 같은 정보를 두개의 별도 클래스들에 주지 않아도 됩니다.


SnippetList, SnippetDetail, SnippetHighlight


다음은, 우리는 SnippetList, SnippetDetail 그리고 SnippetHighlight 뷰 클래스들을 교체 해줄겁니다. 우리는 이 세개의 뷰들을 삭제하고, 하나의 클래스로 대체해줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from rest_framework.decorators import action 
from rest_framework.response import Response


class SnippetViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides 'list', 'create', 'retrieve', 'update' and 'destroy' actions

Additionally, we also provide an extra 'hihghlight' action.
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = [permissions.IsAuthenticateOrReadOnly, IsOwnerOrReadOnly]
@action(detail=True, rendere_classes[renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_objects()
return Response(snippet.highlighted)

def perform_create(self, serializer):
serializer.save(owner=self.request.user)

이번에 우리는 ModelViewSet 클래스를 사용해서, 완벽하게 기본 읽기와 작성 동작들을 설정해주었습니다.


또한 @action 데코레이터를 사용하여, highlight 라는 커스텀 엑션을 생성해 주었다는것도 숙지해야 합니다. 이 데코레이터는 create/update/delete 스타일에 맞지 않는 커스텀 앤드포인트들을 추가할때 사용됩니다.


@action 데코레이터를 사용하는 커스텀 엑션들은 기본적으로 GET 요청들에 반응합니다.


만일 우리가 POST 요청에 반응하는 엑션을 원한다면, 우리는 methods 인자를 사용할수 있습니다.


커스텀 엑션의 URL 들은 기본적으로, 해당 메서드 이름에 따라 결정됩니다. 만약, 우리가 URL 이 생성되는 방식을 바꾸고 싶으면, url_path 를 데코레이터의 키워드 인자로 포함할수 있습니다.


ViewSets & Routers


REST FRAMEWORK 는 ViewSets 를 다루기 위한 추상화된것을 포함하고 있습니다.


이는, 개발자들에게 API 의 상태와 상호작용들을 모델링하는데에 집중할수 있게 해주고. URL 빌드업이 자동으로 이루어지게 해줍니다.


ViewSet 클래스들은, View 클래스들과 거의 같습니다.


get 혹은, put 같은 메서드 대신에, read 혹은 update 같은 동작을 제공한다는점만 제외하고 말이죠.


하나의 ViewSet클래스는 메서드 핸들러에 마지막 순간에 bound 됩니다.


보통, Router 를 사용하여, set of views 로 인스턴스화 되었을때, bound 됩니다.


Router 클래스는, URL conf 를 정의하는데에 있는 복잡함을 다루어 줍니다.


Browsing the API


브라우저를 열어서 브라우저블 API 로 가보면, 이제 우리는 API 사이를 간단하게 링크를 따라가는것만으로도 작업할수 있습니다.



또한, 스니펫 인스턴스에는, highlight 라는 링크를 볼수 있을것이고, 이는 HTML 형태로 스니펫을 하이라이트 해줍니다.


Tutorial Part 6 에서는, 어떻게 ViewSetsRouters를 사용하여 코드의 양을 줄일수 있는지 둘러볼것입니다.



Hyperlinking our API


각 요소들의 관계를 다루는것은 우리의 웹 API 디자인에서 조금 더 어려운 부분입니다.


관계를 표현하기 위해서 선택할수 있는 여러가지 다른 방법들이 존재합니다.


  • primary key 사용하기
  • entities 간 하이퍼링크 해주기
  • 고유한 slug 필드를 관계된 entity 에 사용해주기
  • 관계된 entity 의 기본 문자열 표현 사용하기
  • 부모 표현 안에 관계된 entity 네스팅 해주기
  • 기타 다른 사용자 정의 표현

REST Frmaework 은 이 모든 스타일들을 지원합니다. 그리고, 순방향 혹은 역방향 관계들에 적용할수 있습니다. 혹은, generic foreign key 와 같은 커스텀 메니저에도 적용될수 있습니다.


이번에 우리는, entity 간에 하이퍼링크를 해주는 스타일을 사용해 볼겁니다.


이를 하기 위해서는, 기존에 존재하던 ModelSerializer 대신에, 우리의 serializer 를 수정하여 HyperlinkedModelSerializer 를 확장해야 합니다.


더 읽어보기 »

Creating an endpoint for the highlighted snippets


우리의 API 코드가 코드 하이라이팅에 앤드포인트를 가지고 있지 않습니다.


우리는 JSON 을 사용하지 않고, HTML 로 표시합니다.


REST FRAMEWORK 에서는, 두가지 스타일의 HTML 렌더러가 제공됩니다.


  1. 템플릿을 사용하여, HTML 을 렌더
  2. 미리 렌더가 된 HTML 을 다루는것

2번째 방식이, 우리가 앤드포인트를 위해 사용하고 싶은것 입니다.


코드 하이라이트 뷰를 생성할때 염두해 두어야 할 또다른 한가지는, 우리가 사용할수 있는 강력한 generic 뷰가 존재하지 않는다는 점입니다. 우리는 객체 인스턴스를 반환하지 않지만, 객체 인스턴스의 속성을 대신 리턴해 줄겁니다.


generic 뷰를 사용하는 대신에, 우리는 인스턴스를 표현하기 위한 베이스 클래스를 사용하고, 우리만의 .get() 메서드를 사용할겁니다.


snippets/views.py 파일을 열고, 아래를 추가해 줍니다.


더 읽어보기 »