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

0%

Adding information to our model


우리는, 우리의 Snippet 모델 클래스에, 몇가지 수정사항을 만들겁니다.


우선, 몇가지 필드들을 추가 합시다. 추가할 필드중 하나는, 누가 코드 스니펫을 생성했는가를 표시하기 위해서 사용될것입니다. 또다른 필드는, 하이라이트 된 코드의 HTML 을 저장하는데에 쓰입니다.


  1. 스니펫 생성자 owner 필드
  2. 하이라이트 된 코드의 HTML 텍스트

아래 두가지 필드들을 snippets/models.py 에 있는 Snippet 모델에 추가해 줍니다.


1
2
owner = models.ForeignKey('auth.User', related_name='snippets', 		 on_delete=models.CASCADE)
highlighted = models.TextField()

모델이 저장될때, 또한가지 확실시 해야 하는것은, 코드를 하이라이트 해주는 라이브러리인 pygments 를 사용하여 highlighted 필드를 채워주는것입니다.


몇가지 불러오기를 해줄 필요가 있습니다.


1
2
3
from pygments.lexers import get_lexer_by_name 
from pygments.formatters.html import HtmlFormatter
from pygments import highlight

그리고 이제, 우리는 .save() 메서드를 우리의 모델 클래스에 추가해줄수 있습니다.


1
2
3
4
5
6
7
8
9
10
def save(self, *args, **kwargs):
"""
Use the 'pygments' library to create a highlighted HTML representation of the code snippet.
"""
lexer = get_lexer_by_name(self.language)
linenos = 'table' if self.linenos else False
options = {'title': self.title} if self.title else {}
formatter = HtmlFormatter(style=self.style, linenos=linenos, full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)

이것이 모두 완료가 되면, 우리는 우리의 데이터베이스 테이블들을 업데이트 해줄 필요가 있을것입니다.


이를 해주기 위해서, 일번적으로 우리는 database migration 을 생성해 줍니다. 하지만, 이번 튜토리얼의 목적을 위해, database 를 삭제하고 다시 시작합시다.


터미널에서 아래 명령어를 실행 합니다.


1
2
3
4
rm -f db.sqlite3 
rm -f snipepts/migrations
python manage.py makemigraitons snippets
python manage.py migrate

또한 여러분들은 API 테스팅 사용을 위한, 여러명의 다른 사용자들을 생성하고 싶을지 모릅니다.


이를 위해 가장 빠른 방법은 createsuperuser 커맨드를 사용하는것입니다.


1
python manage.py createsuperuser

Authentication & Permissions


현재 우리의 API 는 누가 code snippets 을 수정을 하든 삭제를 하든지 어떠한 제약도 가지고 있지 않습니다. 우리는 아래의 사항들에 대해 확실시 하기 위해, 조금더 고도의 동작을 만들어 놓고싶습니다.


  • 코드 스니펫들은 언제나 생성자와 연관되어야함
  • 오직 인증된 사용자들만 스니펫을 생성할수 있음
  • 오직 스니펫을 생성한 생성자만, 스니펫을 업데이트 혹은 삭제할수 있음
  • 인증 되지 않은 사용자들은 읽기전용 엑세스만 가져야만 함

Generic 클래스뷰 사용하기


mixin 클래스들을 사용하여서 우리의 뷰들은 그전보다 조금은 적은 양의 코드를 사용하도록 작성했었습니다. 하지만, 한단계 더 줄일수 있습니다.


REST FRAMEWORK 은 이미 mixin 되어 있는 generic 뷰들을 제공하여, 우리가 우리의 views.py 를 조금 더 줄이는데 사용할수 있게 해줍니다.


snippets/views.py 파일을 아래와 같이 수정해 줄수 있습니다. 조금 더 적은 양의 코드가 사용됩니다!


1
2
3
4
5
6
7
8
9
10
11
12
13
from snippets.models import Snippet 
from snippets.serializers import SnippetSerializer
from rest_framework import generics


class SnippetList(generics.ListCreateAPIView):
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Snippet.objects.all()
seriazlizer_class = SnippetSerializer

와~ 엄청나게 간결해 집니다. 다음 튜토리얼에서는, API 에 대한 인증과 권한에 대해서 둘러볼것입니다.


Mixins 사용하기


클래스 기반 뷰를 사용하는것의 이점중 하나는, 어떠한 동작에 대해서 재사용할수 있는 코드를 작성할수 있다는 점입니다.


우리가 API 를 위해 이제까지 사용하고 있었던 create/retrieve/update/delete 동작들은, 일반 Django 에서 우리가 생성하는 모델이 뒷받침 해주는 API View 들 안에서도 굉장히 비슷하게 사용될수 있습니다.


이런 비슷한 동작들은, REST FRAMEWORK 의 mixin 클래스에 구현 되어 있습니다.


Mixins


객체 지향 프로그램 언어들 안에서,

mixin 은 메서드들을 포함하고 있는 하나의 클래스로, 메서드들은 다른 클래스들에 의해서 사용되는데,

다른 클래스가 mixin 에 있는 메서드를 사용하기 위해서, mixin class 가 해당 클래스의 부모 클래스가 될 필요는 없습니다.


때때로, Mixins 들은 상속 개념보다는, 포함된다는 개념으로 묘사되곤 합니다.


예를들면, mixin 클래스안에 있는 메서드를 다른 클래스가 사용을 하는데, 보통은 상속을 받아서 사용하지만, mixin 클래스는 굳이, mixins 클래스 안에 있는 매서드를 다른 클래스가 사용한다해도, mixin 클래스가 다른 클래스의 부모 클래스가 될 필요는 없습니다.


어떻게 mixin 클래스를 사용해서 뷰를 작성할수 있는지 둘러봅시다. snippets/views.py 파일을 다시 봅니다.


더 읽어보기 »

Class based view


우리는 우리의 API 뷰들을 함수 기반 대신에 클래스 기반 뷰들을 이용하여 작성할수도 있습니다.


우리는 이것은 공통된 기능들을 재사용하고, 반복적이지 않게 하는 강력한 패턴으로 볼수 있습니다. 코드를 DRY 하게 해줄수 있습니다 (DRY 는 Don’t Repeat Yourself 의 약자).


Rewriting our API using class-based views


우리의 기존 뷰를 클래스 기반 뷰로 다시 작성하는것부터 시작을 할것 입니다.


이 모든것은 snippets/views.py 를 조금씩 리팩토링 해주면서 진행 합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from snippets.models import Snippet 
from snippet.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status


class SnippetList(APIView):
"""
List all snippets, or create a new snippet.
"""
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)

def post(self, request, format=None):
serializer = SnippetSerializer(data=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)

여기까지 아주 좋습니다. 이전과 꽤 비슷해 보입니다만, 우리는 다른 HTTP 메서드들을 사이에 두고 구분을 짓고 있습니다. 우리는, 우리의 인스턴스 뷰도 업데이트 해줄 것입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class SnippetDetail(APIView):
"""
Retrieve, update or delete a snippet instance.
"""
def get_object(self, pk):
try:
return Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
raise Http404

def get(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet)
return Response(serializer.data)

def put(self, request, pk, format=None):
snippet = self.get_object(pk)
serializer = SnippetSerializer(snippet, 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, format=None):
snippet = self.get_object(pk)
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

여기까지도 좋아 보입니다. 다시 한번, 코드는 여전히 함수 기반 뷰와 꽤나 비슷합니다. snippets/urls.py 또한 클래스 기반 뷰를 사용하기 위해서, 살짝 리펙터를 해줘야 합니다.


1
2
3
4
5
6
7
8
9
10
from django.urls import path 
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
path('snippets/', views.SnippetList.as_view()),
path('snippets/<int:pk>/', views.SnippetDetail.as_views()),
]

urlpatterns = format_suffix_patterns(urlpatterns)


Browsability


API 는 클라이언트 요청에 따라서 응답 컨텐츠 유형을 선택하기 때문에, 웹브라우저에서 해당 리소스가 요청 되었을때. 기본값으로 HTML 형식으로 표현된 리소스를 반환합니다.


웹 브라우저로 볼수 있는 API 를 갖는다는것은, 사용하는데에 많은 이점이 됩니다. 그리고, API 를 개발하고 사용하는것을 쉽게 만들어 줍니다.


또한, 당신의 API 를 작업하고 싶은 다른 개발자들의 진입 장벽을 낮춰 줍니다.


browable 토픽 문서를 보면, browsable API 기능에 대한 더 많은 정보와 어떻게 커스터마이즈를 할수 있는지 볼수 있습니다.


What’s Next?


다음 토픽부터는, 클래스 기반 뷰를 사용하기 시작할겁니다. 어떻게 generic view 들이 우리가 써야하는 코드의 양을 줄여주는지 확인 할수 있습니다.


How’s it looking?


더 나아가서, API 를 커맨드라인에서 테스트 해볼수 있습니다.


우리는, 유효하지 않은 요청을 보냈을때 발생하는 몇가지 더 좋은 에러들을 다루기도 하였지만, 모든것은 비슷하게 작동합니다.


이전처럼, 모든 snippets 의 리스트를 얻을수 있습니다.


서버를 실행하고, 새로운 터미널 창을 열어서, 아래 커맨드를 실행해 줍니다.


1
http http://127.0.0.1:8000/snippets

아래와 같은 리스트가 나옵니다.



응답의 포맷


우리가 다시 받는 응답의 포멧을 제어할수도 있습니다.

Accept 헤더를 사용합니다.


1
2
http http://127.0.0.1:8000/snippets/ Accept:application/json    # JSON 요청 
http http://127.0.0.1:8000/snippets/ Accept:text/html # HTML 요청

JSON


json 요청



HTML 요청



혹은, 포맷 접미사를 붙여서 사용합니다.


1
2
http http://127.0.0.1:8000/snippets.json  # JSON suffix 
http http://127.0.0.1:8000/snippets.api # browsable API siffuix


요청의 포맷 제어


비슷한 방식으로, 우리는 우리가 보내는 요청의 포맷을 제어할수 있습니다

content-type-header 를 사용해서, 요청의 포맷을 제어합니다.



만약 --debug 스위치를 위의 http 요청에 추가한다면, 요청 타입을 요청의 헤더 안에서 볼수 있습니다.


이제, API 를 웹 브라우저에서 열어봅니다 http://127.0.0.1:8000/snippets/



Adding optional format suffixes to our URLs


우리의 응답들이 더이상 하나의 컨텐트 유형에 묶여 있지 않는다는 사실의 장점을 이용하기 위해서, 우리의 API endpoints 에 추가 접미사 포맷을 추가해 줍니다.


format suffixes 를 사용하면, 명시적으로 주어진 포맷을 참조하게 하는 URL 을 가질수 있고, 우리의 API 가 http://example.com/api/items/4.json 같은 URL 들을 다룰수 있다는것을 의미 해줍니다.


format 키워드 인자들을, 두 views 에 추가해주면서 시작해줍니다.

snippets/views.py 파일을 열고,


snippet_listsnippet_detail 에 format 키워드 인자들을 추가해줍니다.


1
2
3
def snippet_list(request, format=None)

def snippet_detail(request, format=None)

urls.py 파일을 업데이트 해주기


이제, snippets/urls.py 파일을 조금 업데이트 해줍니다. format_suffix_patterns 를 기존에 존재하고 있던 URLs 에 추가해 줍니다.


1
2
3
4
5
6
7
8
9
10
from django.urls import path 
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
path('snippets/', views.snippet_list),
path('snippets/<int:pk>', views.snippet_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)

우리는, 이 추가적인 url patterns 들을 꼭 추가해줄 필요는 없지만, 추가해줌으로써 특정 포맷을 참조하는 간단하고 깔끔한 방법을 가질수 있습니다.


Pulling it all together


이제 더 앞으로 나아가서, 새로운 컴포넌트를 사용하여 우리의 views 를 조금 더 리펙터 해봅시다.


snippets/views.py 파일을 아래와 같이 고쳐줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


@api_view(['GET', 'POST'])
def snippet_list(request):
"""
List all code snippets, or create a new snippet
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method == "POST":
serializer = SnippetSerializer(data=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_REQEUST)

우리의 인스턴스 뷰는 지난 예시보다 조금 향상되었습니다. 조금 더 간결하고, 코드는 이제 Forms API 와 작업하는것과 더 많이 비슷한 느낌입니다. 우리는 또한, 이름을 가진 상태값들을 사용하여, 응답들이 의미하는바를 좀 더 명확하게 해줍니다.


개별 snippet 에 대한 뷰는 아래의 예시처럼 고쳐줍니다. snippet/views.py 를 아래와 같이 수정해 줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
"""
Retrieve, update or delete a code snippet.
"""
try:
snippet = Snippet.objects.get(pk=pk)
except Snippet.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)

if request.method == "GET":
serializer = SnippetSerializer(snippet)
return Response(seriazlier.data)

elif request.method == "PUT":
serializer = SnippetSerializer(snippet, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

elif request.method == 'DELETE':
snippet.delete()
return Response(status=status.HTTP_204_NO_CONTENT)

이는 충분히 친근하게 느껴야 합니다 - 일반 Django views 들을 작업하는것과 많이 다르지 않습니다.


우리는 이제 더이상 주어진 컨텐트 종류에 request 혹은 response 들을 묶어주지 않는다는것을 알아 차려야 합니다. request.data 는 들어오는 json 요청들을 다룹니다. 하지만, request.data 는 다른 포멧들을 다룰수 있습니다.


비슷한 방식으로 우리는 데이터를 가진 응답 객체들을 반환하고 있습니다만, REST framework 가 응답을 맞는 형태의 컨텐트 타입을 렌더하게 해줍니다.


Wrapping API Views


REST framework 은 API VIews 를 작성할때 감싸줄수 있는 두가지 레퍼를 제공합니다.


  1. 함수 기반 뷰들을 작성할때 사용할 @api_view 데코레이터
  2. 클래스 기반 뷰들을 작성할때 사용할 APIView 클래스

이 레퍼들은 약간의 기능들을 제공합니다, view 가 Request 인스턴스를 받는것을 확실하게 하거나, Response 객체에 컨텍스트를 추가하여, content negotiation 이 동작하게 만듭니다


이 레퍼들은 또한, 적절한 때에, 405 Method Not Allowed 같은 응답을 반환하는 동작을 제공하기도 합니다. 그리고, 잘못된 데이터로 request.data 에 접근하려 할때 일어나는 그 어떠한 ParseError 도 다루고 있습니다.