23.04.24 TIL - DRF: serialize, renderer, response
오늘 한 것
강의를 듣다가 궁금한 것이 생겨서 하루 꼬리에 꼬리를 물고 찾아다녔다.
아래 내용이 오늘자 탐색의 결과이다.
21:00 현재 선발대 강의 내용 정리중
문제: test.py에서 response 까보기
문제상황은 다음과 같다. test.py에서 회원가입 API를 검증하려고 한다. 회원가입 API는 유효한 입력일 때 DB에 유저 데이터를 생성하고 응답에 생성된 유저데이터를 함게 실어 보낸다. 나는 test.py에서 입력된 user의 email과 응답에 실려온 유저데이터의 email을 비교해ㅗ고자 한다.
시도 1. response를 parse 해보기
우리가 까보려고 하는 email값은 response에 존재하며 이는 serializer에 의해 직렬화된 데이터일 것이다. 왜냐하면 우리가 views.py에서 직렬화 하여 response에 담아 반환하였기 때문이다. 따라서 response안에 JSON형식으로 담겨 있을 것이라 생각하여 프론트에서 response.json()으로 데이터를 deserialize하는 것과 같이 접근하려 하였다.
...
import json
class UserRegisterAPIViewTestCase(APITestCase):
def test_register(self):
url = reverse("user_view")
user_data = {"email": "abcd@naver.com", "password": "1234!!1234!!"}
response = self.client.post(url, user_data)
answer= json.loads(response)["email"]
self.assertEqual(user_data["email"], answer)
TypeError: the JSON object must be str, bytes or bytearray, not Response
그러나 위와같은 오류가 발생하였다. 이는 Response가 문자열, 비트열이 아니므로 JSON이 아니란 뜻이다.
시도2. response를 딕셔너리형태로 출력시켜보기
response = self.client.post(url, user_data)
print(response.__dict__)
{
...
'data': {'id': 1, 'password': '...', 'last_login': None, 'email': 'abcd@naver.com', 'is_active': False, 'is_admin': False, 'followings': []},
...
}
원하는 데이터(가입한 사용자 정보)가 data 속성에 딕셔너리로 깔끔하게 들어가 있다.
그러면
class UserRegisterAPIViewTestCase(APITestCase):
def test_register(self):
url = reverse("user_view")
user_data = {"email": "abcd@naver.com", "password": "1234!!1234!!"}
response = self.client.post(url, user_data)
print(response.data)
self.assertEqual(user_data["email"], response.data["email"])
이렇게 해봤더니 잘 된다.
하지만 여전히 의문이 남는다. 대체 왜 serializer의 결과물이 data에 딕셔너리 형태로 존재하는가?
해결: Docs를 읽어보자
Response(data, status=None, template_name=None, headers=None, content_type=None)
View 클래스의 메소드가 반환하는 Response는 위와같은 인자들을 받을 수 있다. data 에는 render 되지 않은 데이터가 들어간다. render는 응답의 형식을 적절한 형태로 바꾸는 것을 말한다. 즉 data에는 실제로 json이 아닌 원본 데이터가 들어갈 수 있다.
status에는 말 그대로 상태코드를 포함한 상태를 보내고 template_name은 만약 HTMLRenderer가 선택 되었을 경우 렌더링할 html의 경로다. headers와 content-type은 말 그대로다. 기본적으로 자동으로 채워진다.
Response는 다음과 같은 속성들을 가진다.
.data: render 되지 않은 serialized data를 가진다.
.status_code: 상태코드
.content: Render된 데이터. 이 속성에 접근하기 전에 .render() 메소드가 실행되어야한다.
.template_name: 인자와 동일
.accepted_renderer: Render할 때 어떤 Renderer를 사용할지 정함. 반환되기 전에 자동적으로APIView or @api_view에 의해 설정된다.
.accepted_media_type: content negotiation 단계에서 선택된 media-type. 반환되기 전에 자동적으로APIView or @api_view에 의해 설정된다.
.renderer_context: Renderer의 .render() 메소드에 넘겨줄 추가적인 context 정보 딕셔너리.
반환되기 전에 자동적으로APIView or @api_view에 의해 설정된다.
.data 와 .content를 보면, content가 내가 생각했던 json화 된, 즉 문자열 혹은 바이트열 형태의 데이터이다. 따라서 다음과 같이 해도 시도2와 같은 결과를 얻을 수 있다.
...
import json
class UserRegisterAPIViewTestCase(APITestCase):
def test_register(self):
url = reverse("user_view")
user_data = {"email": "abcd@naver.com", "password": "1234!!1234!!"}
response = self.client.post(url, user_data)
answer= json.loads(response.content)["email"]
self.assertEqual(user_data["email"], answer)
남은 의문 1: 왜 data엔 json이 없는가? serializer는 무엇을 하는가?
공식 문서를 보자 다음과 같은 내용이 보인다.
We can now use CommentSerializer to serialize a comment, or list of comments. Again, using the Serializer class looks a lot like using a Form class.
At this point we've translated the model instance into Python native datatypes. To finalise the serialization process we render the data into json.
즉, serializer는 일단 데이터를 '파이썬의 기본 자료형'을 상속하는 타입으로 만든다.
그리고 나서 renderer가 JSON화 시키는 것이다.
from rest_framework.renderers import JSONRenderer
json = JSONRenderer().render(serializer.data)
# b'{"email":"leila@example.com","content":"foo bar","created":"2016-01-27T15:17:10.375877"}'
실제로 랜더러를 거치지 않았을 response.data(=serializer의 반환값)의 타입을 보면 다음과 같다.
<class 'rest_framework.utils.serializer_helpers.ReturnDict'>
찾아보면 ReturnDict는 OrderedDict을, OrderedDict는 딕셔너리 클래스를 상속받고 있다.
따라서 여기서 serializer.data는 실제로 딕셔너리인 것이다.
이를 통해 시도 2의 결과를 설명할 수 있다.
serializer는 위와같이 데이터를 python native datatype으로 바꾸는 역할을 수행한다.
남은 의문2. Serialize(직렬화)는 JSON화 시킨다는 뜻이 아닌가?
찾아본 결과, 그런 뜻이 아니었다. 일단 영문 위키백과에서 제공하는 serialization의 정의는 다음과 같다.
In computing, serialization (or serialisation) is the process of translating a data structure or object state into a format that can be stored (e.g. files in secondary storage devices, data buffers in primary storage devices) or transmitted (e.g. data streams over computer networks) and reconstructed later (possibly in a different computer environment).
즉 객체를 저장가능하거나 전송 가능한 형태로 만들기 위해 객체의 데이터를 연속적인 데이터스트림으로 만드는 것이 serialization이다.
스택에 실제 값을 저장하고 있는 경우가 아닌, 힙의 특정 영역 주소값만 스택에 저장된 데이터들이나 내부적으로 메모리에 연속적으로 저장되지 않은 데이터들은 저장이나 전송을 할 수 없기 때문이다.
DRF의 serializer는 바로 데이터 스트림으로 만들어 주진 않지만 JSON renderer가 쉽게 데이터스트림(바이트열, 문자열)로 만들 수 있는 딕셔너리 데이터로 만들어준다.
그렇다면 serialization의 결과물이 JSON이 아닐수도 있는가? 맞다. 다른 serialization 형식중 하나로 '피클'이 있는데 JSON과 마찬가지로 표준 라이브러리가 제공하는 직렬화 형식이다.
import pickle
person = {"name": "Jessa", "country": "USA", "telephone": 1178}
# save dictionary to person_data.pkl file
with open('person_data.pkl', 'wb') as fp:
pickle.dump(person, fp) # 저장
with open('person_data.pkl', 'rb') as fp:
person = pickle.load(fp) # 불러옴
남은 의문3. 그럼 Renderer는 무엇인가?
REST framework includes a number of built in Renderer classes, that allow you to return responses with various media types.
즉, 렌더러는 response에 담긴 serialized된 데이터를 다양한 형태로 표현할 수 있도록 해주는 클래스 들이다.
CBV에서는 다음과 같이 클래스의 리스트로 정해서 쓴다.
from rest_framework.renderers import TemplateHTMLRenderer
from rest_framework.generics import RetrieveAPIView
class PostDetailAPIView(RetrieveAPIView):
queryset = Post.objects.all()
renderer_classes = [TemplateHTMLRenderer] # 템플릿으로 렌더링
template_name = 'blog/post_detail.html'
def get(self, request, *args, **kwargs):
return response({...},template_name=template_name)
정하지 않을 경우 기본 설정에 따라가며, DRF 기본 설정은 다음과 같다.
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
]
}
클래스의 리스트 형태로 정하므로 여러 렌더러를 함께 지정할 수 있으며 클라이언트가 보내는 Accept헤더에 따라 다른 형태로 보낼 수 있다. 첫번째 것이 우선된다.
JSONRenderer는 utf-8 인코딩을 이용해 JSON형태로 만들어 준다.
{"unicode black star":"★","value":999}
.media_type: application/json
.format: 'json'
.charset: None
BrowsableAPIRenderer는 데이터를 Browserable API를 위한 HTML형태로 만들어준다.
.media_type: text/html
.format: 'api'
.charset: utf-8
.template: 'rest_framework/api.html'
TemplateHTMLRenderer는 데이터를 지정한 HTML형태로 만들어준다. 데이터의 직렬화가 필요없다.
class UserDetail(generics.RetrieveAPIView): # read-only endpoints to represent a single model instance.
queryset = User.objects.all()
renderer_classes = [TemplateHTMLRenderer]
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return Response({'user': self.object}, template_name='user_detail.html')
.media_type: text/html
.format: 'html'
.charset: utf-8
그외에도 다양한 Renderer가 존재한다.
오늘 배운 점:
공식 문서는 성경과도 같다. 앞으로 영어가 힘들어도 공식문서를 잘 읽자.
Serialization의 명확한 의미에 대해 알게 되었다.
DRF에서 어떻게 응답을 처리해서 전송하는지 알 수 있었다.
레퍼런스
https://kimdoky.github.io/django/2018/07/10/drf-Renderers/
Django REST Framework - Renderers
on July 10, 2018 under django 16 minute read Django REST Framework - Renderers “Before a TemplateResponse instance can be returned to the client, it must be rendered. The rendering process takes the intermediate representation of template and context, an
kimdoky.github.io
https://en.wikipedia.org/wiki/Serialization
Serialization - Wikipedia
From Wikipedia, the free encyclopedia Conversion process for computer data In computing, serialization (or serialisation) is the process of translating a data structure or object state into a format that can be stored (e.g. files in secondary storage devic
en.wikipedia.org
Serialize란 무엇인가?
코드를 쓰다 옆사람이 serialize가 뭔데 썼냐는 말에 저는 그냥 남들이 사용하길래 적었다. 라는 말도안되는 대답을 했습니다. serialize가 무엇이고 왜 사용하는지 궁금하여 기록한 내용입니다. Seria
ahma.tistory.com
https://www.django-rest-framework.org/api-guide/responses/#data
Responses - Django REST framework
response.py Unlike basic HttpResponse objects, TemplateResponse objects retain the details of the context that was provided by the view to compute the response. The final output of the response is not computed until it is needed, later in the response proc
www.django-rest-framework.org
https://www.django-rest-framework.org/api-guide/serializers/?q=RetrieveAPIView
Serializers - Django REST framework
www.django-rest-framework.org