파이썬/장고 웹서비스 개발 완벽 가이드 with 리액트 강의를 듣고 정리한 글입니다.
모든 프로그래밍 언어의 통신에서 데이터는 필히 문자열로 표현되어야만 한다.
각 언어에서 모두 지원하는 직렬화 포맷(JSON, XML 등)도 있고 특정 언어에서만 지원하는 직렬화 포맷(파이썬은 Pickle)이 있다.
데이터는 같아도 응답 형식이 다를 수 있다.
application/x-www-form-urlencoded
인코딩 혹은 multipart/form-data
인코딩으로 요청하고, HTML 포맷으로 응답json.dumps(post_list)
실행 시 str타입으로 저장된다.import json
# 데이터 준비
post_list = [
{'message': 'hello askdjango'},
]
# 직렬화
json_string = json.dumps(post_list) # str type
print(json_string) # '[{"message": "hello askdjango"}]'
# 비직렬화 -> 다시 객체
print(json.loads(json_string))
pickle.dumps(post_list)
실행 시 bytes타입으로 저장된다.import pickle
# 데이터 준비
post_list = [
{'message': 'hello askdjango'},
]
# 직렬화
pickle_bytes = pickle.dumps(post_list) # bytes type
print(pickle_bytes) # b'\x80\x03]q\x00}q\x01X\x07\x00\x00\x00messageq\x02X\x0f\x00\x00\x00hello askdjangoq\x03sa.'
# 비직렬화 -> 다시 객체
print(pickle.loads(pickle_bytes))
TypeError: Object of type "???" is not JSON serializable
python3 manage.py shell
>>> import json
>>> from django.contrib.auth import get_user_model
>>> User = get_user_model()
>>> json.dumps(User.objects.first())
TypeError: Object of type "???" is not JSON serializable
json/pickle 모두 파이썬 기본 라이브러리
→ 장고 타입(Model/QuerySet 등)에 대해서는 직렬화 Rule이 없음.
다음 타입에 대한 직렬화 Rule을 추가로 구현한다.
datetime.datetime
, datetime.date
, datetime.time
, datetime.timedelta
decimal.Decimal
, uuid.UUID
, Promise
json.dumps에 cls인자로 넘겨서 사용한다. →ex. json.dumps({{ data }}, cls=DjangoJSONEncoder)
import json
from django.core.serializers.json import DjangoJSONEncoder
from django.contrib.auth import get_user_model
qs = get_user_model().objects.all()
json_string = json.dumps(qs, cls=DjangoJSONEncoder) # 위의 룰은 해결 가능하나, qs에 대해서는 동일한 오류가 발생한다.
print(json_string)
data = [
{'id': post.id, 'title': post.title, 'content': post.content}
for post in Post.objects.all()
]
json.dumps(data)
# PS. 추가 인자 설명
# ensure_ascii=False : 유니코드로 표현
# .encode('utf8') : 특정 인코딩으로 변환(utf-8)
json.dumps('한글', ensure_ascii=False).encode('utf8')
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models.query import QuerySet
# 커스텀 JSON Encoder를 정의
class MyJSONEncoder(DjangoJSONEncoder):
def default(self, obj):
if isinstance(obj, QuerySet):
"""
1. obj가 QuerySet type의 인스턴스인 경우에는 강제로 tuple로 변환하겠다.
2. QuerySet 내에서도 tuple로 변환 도중에 여러 값이 있다.
3. 2에서 변환도중의 여러 값에도 재귀적으로 이 로직을 타서 튜플로 변환한다.
"""
return tuple(obj)
elif isinstance(obj, Post):
"""Post모델 타입인 경우에는 아래와 같이 사전타입으로 변환하겠다."""
return {'id': obj.id, 'title': obj.title, 'content': obj.content}
elif hasattr(obj, 'as_dict'):
"""obj에 as_dict()라는 메소드가 있다면, 호출해서 반환값을 리턴하겠다."""
return obj.as_dict()
return super().default(obj) # DjangoJSONEncoder가 구현한 기본 로직을 쓰겠다.
data = Post.objects.all()
# 직렬화할 때, 직렬화를 수행해줄 JSON Encoder를 지정해줍니다.
json.dumps(data, cls=MyJSONEncoder, ensure_ascii=False)
rest_framework.utils.encoders.JSONEncoder
datetime.datetime
/date
/time
/timedelta
, decimal.Decimal
, uuid.UUID
, six.binary_type
를 대응한다.__getitem__
속성을 지원할 경우 dict(obj) 변환__iter__
속성을 지원할 경우, tuple 변환QuerySet
타입일 경우, tuple
변환.tolist
속성을 지원할 경우. obj.tolist()
반환rest_framework.renderer.JSONRenderer
json.dumps에 대한 래핑 클래스이다. 보다 편리한 직렬화 지원하며 UTF8 인코딩도 추가로 수행한다.
내부적으로는 JSONEncoder를 사용한다.
이를 커스텀하여 Response 스키마를 디자인하기도 하는거같다.
from rest_framework.renderers import JSONRenderer
data = Post.objects.all()
JSONRenderer().render(data) # 오류.
# rest_framework.utils.encoders.JSONEncoder에 정의된 Rule에 대응된 객체만 직렬화 가능.
Serializer/ModeSerializer는 Form/ModelForm과 유사하다.
→ 역할 면에서 Serializer는 POST요청만 처리하는 Form이라 할 수 있다.
장고의 Form/ModelForm vs DRF의 Serializer/ModelSerializer
Form / ModelForm
: Form태그가 포함된 HTML을 생성Serializer / ModelSerializer
: Form 데이터가 포함된 JSON 문자열을 생성QuerySet 또는 Model를 ReturnList 또는 ReturnDict 타입으로 반환해준다.
이러한 ReturnDict 또는 ReturnList는 json직렬화가 가능한 객체이다.
ModelSerializer 정의
from rest_framework.serializers import ModelSerializer
# Post모델에 대한 ModelSerializer 정의
class PostSerializer(ModelSerializer):
class Meta:
model = Post
fields = '__all__'
post = Post.objects.first() # Post 타입
serializer = PostSerializer(post)
serializer.data # -> ReturnDict 타입
ReturnDict 또는 ReturnList로 반환하기
Model 객체 및 QuerySet에 대해 ReturnDict 타입으로의 변환이 가능하다.
from myapp.serializers import PostSerializer
from myapp.models import Post
# 한개의 모델만 넘기기
serializer = PostSerializer(Post.objects.first())
serializer.data # {'id': 1, 'message': '첫번째 포스팅' ... }
# 쿼리셋을 넘기기
serializer = PostSerializer(Post.objects.all(), many=True)
serializer.data # [OrderDict([('id', 1), ...)]), OrderDict([('id': 2), ...)]) ...]
ReturnDict 타입?
class ReturnDict(OrderedDict):
def __init__(self, *args, **kwargs):
self.serializer = kwargs.pop('serializer')
super().__init__(*args, **kwargs)
Model 객체에 대해서는 필히
many=False
지정(디폴트)
QuerySet 객체에 대해서는 필히many=True
지정
→ 지정이 맞지 않으면 변환 에러 발생 (TypeError, AttributeError)
일단, ReturnDict나 ReturnList로 반환 후 직렬화를 하면 된다.
qs = Post.objects.all()
serializer = PostModelSerializer(qs, **many=True**)
serializer.data # QuerySet -> ReturnList or Model -> OrderedDict
# 파이썬 기본 JSON 변환(직렬화) 사용
import json
json_str_string = json.dumps(serializer.data, ensure_ascii=False)
# DRF에서 지원하는 JSON 변환(직렬화) 활용 -> 변환 Rule이 추가된 Encoder
from rest_framework.renderers import JSONRenderer
json_utf8_string = JSONRenderer().render(serializer.data)
Serializer의 to_representation 메소드에서 OrderDict로 변환하고
Serializer의 data 프로퍼티에서 ReturnDict라는 클래스로 래핑해서 반환한다.
class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
# ...
def to_representation(self, instance):
"""
Object instance -> Dict of primitive datatypes.
"""
ret = OrderedDict()
fields = self._readable_fields
for field in fields:
try:
attribute = field.get_attribute(instance)
except SkipField:
continue
# We skip `to_representation` for `None` values so that fields do
# not have to explicitly deal with that case.
#
# For related fields with `use_pk_only_optimization` we need to
# resolve the pk value.
check_for_none = attribute.pk if isinstance(attribute, PKOnlyObject) else attribute
if check_for_none is None:
ret[field.field_name] = None
else:
ret[field.field_name] = field.to_representation(attribute)
return ret
json.dumps
를 통해 직렬화된 문자열을 획득하여 HttpResponse를 통해 응답위에서 정리한 MyJSONEncoder를 활용
qs = Post.objects.all()
**# JsonResponse 생성자의 각종 인자** 나열
encoder = MyJSONEncoder
safe = False # True: data가 dict일 경우, False: dict이 아닐 경우
json_dumps_params = {'ensure_ascii': False}
kwargs = {} # HttpResponse에 전해지는 Keyword 인자
from Django.http import JsonResponse
response = JsonResponse(qs, encoder, safe, json_dumps_params, **kwargs)
qs = Post.objects.all()
serializer = PostModelSerializer(qs, many=True)
from rest_framework.response import Response
response = Response(serializer.data) # Content-Type: text/html 디폴트 지정
Response에서는 "JSON 직렬화"가 Lazy하게 동작한다.
실제 응답 생성 시에.rendered_content
속성에 접근하며, 이 때 변환이 이루어진다.
Class based view : APIView
Function based view: @api_view
from rest_framework.views import APIView
renderer_cls = APIView.renderer_classes[0]
renderer_obj = renderer_cls()
response.accepted_renderer = renderer_obj # JSON 변환을 위한 JSONRenderer 인스턴스
response.accepted_media_type = renderer_obj.media_type # 'application/json'
response.renderer_context = {'view': None, 'args': (), 'kwargs': {}, 'request': None}
response # <Response status_code=200, "application/json">
아래는 List기능을 지원하는 코드다. (list, create, detail, update, delete등을 모두 지원하려면 ViewSet을 활용하면 된다.)
from rest_framework import generics
class PostListAPIView(generics.ListAPIView): # generics.ListCreateAPIView -> list, create를 지원한다.
queryset = Post.objects.all()
serializer_class = PostModelSerializer
post_list = PostListAPIView.as_view() # path('public/', views.PostListAPIView.as_view()),를 urls.py의 urlpatterns에 추가해서 사용한다.
author = FK(user)
필드가 있을 때, Serializer에서는 FK키값으로 응답from rest_framework import serializers
from .models import Post
class PostSerializer(serializers.ModelSerializer):
username = serializers.ReadOnlyField(source='author.username') # 추가
class Meta:
model = Post
fields = ['pk', 'username', 'title', 'cotnent'] # 'username' 추가
from django.contrib.auth import get_user_model
from rest_framework import serializers
from .models import Post
class AuthorSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = ['username']
class PostSerializer(serializers.ModelSerializer):
author = AuthorSerializer()
class Meta:
model = Post
fields = '__all__'