Serializer(노션으로 이동완료)

2022. 5. 26. 10:58

1.Serializer

  • 직렬화를 해주는 일종의 도구
  • 장고에만 특별히 존재하는 기능이라기보다는 대부분의 프레임워크에 존재하는 일반적인 개념임

 

장고 Form과의 유사성

  • 장고의 Serializer는 장고의 Form(Model Form) 과 많이 유사하다.
  • 기능적으로 비교하자면 Serializer는 POST요청만 처리하는 FORM이라고 볼 수 있음. 
    • Form : Form 태그가 포함된 HTML 태그를 생성
    • Serializer : Form 데이터가 포함된 JSON문자열을 생성
  • 둘 다 입력된 데이터에 대한 유효성 검사 진행 가능

 

Serializer의 기능

  • 데이터 변환 및 직렬화 지원(JSON 포맷 등)
  • 입력된 JSON 포맷에 대한 유효성검사
  • LIST/CREATE을 포함하여 특정 record(데이터)에 대한 retrieve / edit / delete 등에서 활용
  • APIView를 통한 단일 뷰처리 및 ViewSet을 통한 2개의 뷰를 처리

 

2.Serializer를 활용한 직렬화

직렬화의 두 단계

  • 모델 객체를 데이터 송신을 위한 자료형(str,bytes)로 변환하는 직렬화는 다음과 같이 2단계로 구분할 수 있다. 
    • 모델 객체를 retundict/returnlist로 전환하는 1단계
    • returndict/returnlist를 송신을 위한 자료형으로 전환하는 2단계
  • serialzier는 오로지 1단계만 수행한다. 나머지 2단계는 drf의 다른 함수가 수행하는 것
  • 실제 장고 (apiview를 상속받는)뷰에서는 데이터를 1단계까지만, 즉 returndict / returnlist상태까지만 만든 직후에 'response'라는 함수를 사용하여 바로 통신을 보낸다
    • reponse가 데이터를 송신하는 역할뿐 아니라, returndict / returnlist를 json통신에 적합한 형태로 변환시켜주는 역할(2단계)까지 담당하고 있는 것

 

1단계 : 모델 객체 >>> retundict //return list

  • serializer의 첫번째 인자 또는 'instance'라는 키워드로 모델 객체 또는 쿼리셋을 넣어줄 수 있다.
    • 객체가 아닌 쿼리셋을 넣어줄 경우, 반드시 'many=True'라는 속성값을 넣어줘야 한다.
  • 넣어준 객체/쿼리셋을 returndict/(객체들을 orderdict형태로 변환시켜 감싸고 있는)returnlist형태로 .data에 저장
    • serialzier를 활용하여 모델 객체 또는 쿼리셋에 대한 1단계 직렬화 정도로 생각해도 될듯함
snippet1 = Snippet.objects.first()

#객체를 넣음
serializer_alone = SnippetSerializer(snippet1).data
print(serializer_alone)
>>> {'id': 3, 'title': '', 'code': 'foo = "bar"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
print(type(serializer_alone))
>>> <class 'rest_framework.utils.serializer_helpers.ReturnDict'>

#쿼리를 넣음 >> many=True
serializer_together = SnippetSerializer(Snippet.objects.all(),many=True).data
print(serializer_together)
>>> [OrderedDict([('id', 3), ('title', ''), ('code', 'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 4), ('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
print(type(serializer_together))
>>> <class 'rest_framework.utils.serializer_helpers.ReturnList'>

 

2단계 : returndict // returnlist >> str / bytes

  • serializer를 통해 반환된 returndict(객체)나 returnlist(쿼리셋)들은 str이나 bytes로 변환되어야 통신될 수 있다.
    • str로 전환시키기 위해 json.dumps()를 사용
    • bytes로 전환시키기 위해 JSONRenderer().render(serializer.data)를 사용
  • 2단계가 완료되면 통신을 위한 데이터의 변환이 완벽히 수행된 것. 장고가 없어도 통신이 가능할 정도
  • 원래 drf의 apiview에서는 reponse가 이부분을 담당
#객체
rendered_alone = JSONRenderer().render(serializer_alone)
dumped_alone = json.dumps(serializer_alone)
print(rendered_alone)
>>> b'{"id":3,"title":"","code":"foo = \\"bar\\"\\n","linenos":false,"language":"python","style":"friendly"}'
print(type(rendered_alone))
>>> bytes
print(dumped_alone)
>>> {"id": 3, "title": "", "code": "foo = \"bar\"\n", "linenos": false, "language": "python", "style": "friendly"}
print(type(dumped_alone))
>>> str

#쿼리셋
rendered_together = JSONRenderer().render(serializer_together)
dumped_together = json.dumps(serializer_together)
print(rendered_together)
>>> b'[{"id":3,"title":"","code":"foo = \\"bar\\"\\n","linenos":false,"language":"python","style":"friendly"},{"id":4,"title":"","code":"print(\\"hello, world\\")\\n","linenos":false,"language":"python","style":"friendly"}]'
print(type(rendered_together))
>>> bytes
print(dumped_together)
>>> '[{"id": 3, "title": "", "code": "foo = \\"bar\\"\\n", "linenos": false, "language": "python", "style": "friendly"}, {"id": 4, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly"}]'
print(type(dumped_together))
>>> str

 

3. serializer를  활용한 유효성 검사 및 저장

비직렬화의 세 단계

  • 수신받은 데이터를 모델객체로 저장하는 비직렬화는 다음과 같이 총 세단계로 구분할 수 있다.
    • 수신받은 데이터를 적절한 파이썬 자료형(dict)으로 전환하는 1단계
    • 파이썬 자료형으로 전환된 데이터를 serialzier가 data라는 인자로 받아 유효성 검사를 실행하고, 유효성 검사를 통과할 경우 .validated_data에 ordereddict라는 형태로 저장하는 2단계
    • save()를 호출하여 validated_data에 저장된 데이터를 모델 객체로 직접 저장하는 3단계
  • 이중 serializer는 2,3단계에만 관여함. 즉 serializer가 역직렬화의 모든 부분을 담당하지는 않는다.
    • 반대로 말하면 drf(혹은 장고)의 다른 어디선가 1단계를 수행해줘야 한다는 이야기
    • 그리고 그렇게 1단계를 수행해주는 부분이 request.data
  • drf나 장고의 클래스 apiview(혹은 그냥 view)에서는 클라이언트로부터 송신된 정보를 request.data를 활용하여 조회한다
    • 근데 request.data는 클라이언트로부터 송신된 정보를 자동으로 파이썬 dict형태로 정리해줌
    • 즉 자동으로 비직렬화의 1단계를 처리해줘서, serializer가 request.data를 data인자로 바로 받아서 유효성 검사(비직렬화 2단계)를 수행할 수 있는 것 

 

비직렬화 1단계 : str, bytes를 적합한 파이썬 자료형(dict)로 전환

  • 실제로 사용되는 drf나 장고의 view에서는 클라이언트로부터 수신한 정보가 request.data에 파이썬 dict형태로 저장됨
    • 즉 현재 단계(1단계)를 따로 고려하지 않아도 괜찮다. 그러나 원리를 기록해두려는 의도에서 이부분을 기록함
  • 수신받은 데이터의 자료형이 str(dumped_alone) , bytes(rendered_alone)이라고 가정하고, 두가지 자료형을 파이썬 dict자료형으로 전환
    • bytes자료형을 dict로 바꿀때의 방식이 생소한데, 한번 더 확인해볼 것
#자료형 확인
type(rendered_alone)
>>> bytes
type(dumped_alone)
>>> str


#Case1)bytes를 파이썬 dict자료형으로 전환
import io
stream = io.BytesIO(rendered_alone)
data = JSONParser().parse(stream)
type(data)
>>> dict
print(data)
>>> {'id': 3, 'title': '', 'code': 'foo = "bar"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}

#Case2)str을 파이썬 dict자료형으로 전환
data = json.loads(dumped_alone)
type(data)
>>> dict
print(data)
>>> {'id': 3, 'title': '', 'code': 'foo = "bar"\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}

 

비직렬화 2단계 : 파이썬 자료형(dict)을 ordered_dict로 전환

  • serializer는 두번째 인자 혹은 'data' keyword로 받은 대상에 대해서 유효성검사를 진행함
    • 이때 data로 받는 값은 하나의 객체에 대한 파이썬 dict자료형이여야 한다(쿼리셋도 안됨!!)
    • 그렇지 않으면 유효성검사에 무조건 실패함(검사자격자체가 없다 정도로 이해하면 될 듯)
    • 이 부분때문에 비직렬화 1단계가 필요했던 것
  • serializer에 설정된 모든 필드에 대하여 유효성 검사를 실행
  • 모든 필드에 대한 유효성 검사가 성공적으로 수행될 경우, 유효성 검사가 끝난 값들이 .validated_data에 ordereddict자료형으로 저장된다
  • 만약 필드들 중 단 하나에 대해서라도 에러가 발생한다면 유효성 검사에 실패하고, validated_data에는 어떤 값도 저장되지 않는다(트랜잭션 같은 느낌)
serializer = SnippetSerializer(data = data)
serailizer.is_valid()
>>> True
serializer.validated_data
>>> OrderedDict([('title', ''),('code', 'foo = "bar"'),('linenos', False),('language', 'python'),('style', 'friendly')])

 

비직렬화 3단계 : str, bytes를 적합한 파이썬 자료형(dict)로 전환

  • 유효성 검사를 통과하여 .validated_data에 ordereddict형태로 저장된 값을 .save()를 활용해 모델 객체로 직접 저장하는 단계
  • 참고로, is_valid()를 호출해서 유효성 검사를 통과하지 않으면 save()를 호출할 수 없다.(어차피 호출되도 validated_data가 비워져 있어서 쓸 수도 없음)
  • 또한 save()는 instace의 pk가 db에 있는지 먼저 탐색하여, pk가 존재하지 않을 경우 serializer의 create()메소드를, pk가 존재할 경우 serializer의 update()메소드를 호출한다.
#모델에 객체로 저장
serializer.save()
>>> <Snippet: Snippet object (7)>

 

추가)Serializer.save()

  • 아래 예시는 form과의 차이점을 통해 serializer.save()의 특징을 보여줌
  • 유효성 검사를 통과한 값들이 모인 .validated_data와 kwargs로 받는 값을 합친 데이터를 저
    • 따라서 form에서 처럼 commit=False와 같은 설정값을 준 후 필수 값들을 다시제공해줄 필요없이, 필수적인 값들을 바로 kwarg로 제공해줄 수 있음
  • 저장된 객체를 반환
    •  saved_post는 저장된 객체가 반환되어 있음.
#form의 경우
form = PostForm(request.POST)
if form.is_valid():
	post = form.save(commit=False)#필수값이 없으므로, 바로 저장시 에러가 발생함
    post.author = request.user #필수값 제공
    post.save() #필수값이 제공되었으니 저장될 수 있음(오류 안남)
    
#serializer의 경우
serializer.is_valid()
saved_post = serializer.save(author=request.user) #필수값인 author를 kwarg로 바로 제공함
#saved_post에는 방금 serializer를 통해 저장된 객체가 반환되어 있음

 

추가)returndict

  • returndict는 파이썬 자료형인 dict에 기능이 조금 더 추가된 것이다. 즉, dict가 할 수 있는 것은 returndict도 할 수 있다!
  • 그러한 관점에서보면 직렬화와 비직렬화의 차이를 이해할 수 있을 듯 하다
    • 직렬화는 returndict를 바로 str/bytes형태로 바꿔줄 수 있는 반면, 비직렬화는 str/bytes를 dict로 만든 후 returndict로 만들어준다. 즉, 비직렬화의 경우에만 dict를 만드는 과정이 포함된다.
    • 이건는 dict가 할 수 있는 건 returndict도 할 수 있기 때문임
      • 직렬화의 과정에서 dict가 str/bytes가 될 수 있다면, returndict도 str/bytes가 될 수 있으므로, returndict를 dict로 바꿔주는 과정없이 단박에 returndict를 str/bytes로 전환
      • 반면, 비직렬화시에, dict는 모델객체가 될 수 없으나, dict에서 몇가지 기능이 추가된 returndict는 모델객체가 되어 저장될 수 있음
      • 따라서 str/bytes를 dict로 만들고, dict에 몇가지 기능을 더 추가하여 returndict로 만들고, returndict를 모델객체로 만들어 db에 저장

 


4. Serializer / Model Serializer 생성 및 활용

Serializer/Model Serializer 생성

  • form/modelform과 전반적으로 유사하다
  • serializer.py에 생성하는 것이 일반적
  • serializer는 각 필드에 대한 설정뿐 아니라, 모델 인스터스(객체)생성 및 업데이트를 위한 create,update 메서드를 설정해줘야 한다
  • 반면 modelserializer는 자기가 알아서 create,update메서드를 만들어준다. 물론 커스터마이징이 필요할 경우 오버라이딩을 하면 됨
from rest_framework import serializers

#serializer
class PostSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created_at = serializers.DateTimeField()
    
    def create(self, validated_data):        
        return Snippet.objects.create(**validated_data)

	def update(self, instance, validated_data):
        instance.email = validated_data.get('email', instance.email)
        instance.content = validated_data.get('content', instance.content)
        instance.created_at = validated_data.get('created_at', instance.created_at)
        instance.save()
        return instance
    
#ModelSerializer    
class PostModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = '__all__'

 

ㄴSerializer/Model Serializer 활용(in View)

  • serializer가 설정된 필드값들에 대해(설정되지 않았으면 실제 모델에 있든 없든 상관이 없음) 직렬화 또는 유효성검사를 진행
    • 다만 특정 모델에 대한 모든 필드를 serializer로 설정하지 않을 경우 , POST 메서드 등으로 데이터를 받고 실질적으로 DB에 저장하는 순간 모든 데이터에 대한 값이 존재하지 않아서(정확히 말하면 필수적인 필드들 중 데이터가 없는 경우가 있어서) 데이터가 저장되지 않을 수도 있음
  • 첫번째 인자 혹은 'instance' keyword로 받은 모델 객체나 쿼리셋을 직렬화 시켜주는 기능
    • 보통 조회목적의 GET메서드에 많이 사용. 데이터를 직렬화 시켜서 반환해준다
  • 두번째 인자 혹은 'data' keyword로 받은 대상에 대해서 유효성검사를 진행함
    • 클라이언트로부터 데이터를 먼저 받는  post나 patch 메서드등에 많이 사용
    • 데이터를 받아서 유효성 검사를 진행하고, 유효성 검사를 통과할 경우 데이터를 저장 및 작업을 진행(수정)
    • 통과하지 못할경우 에러를 반환(저장 및 기능 수행 X)
  • instance와 data keyword를 함께 사용할 수도 있다.
    • 기존 db에 저장된 데이터를 가져오고 + 클라이언트로부터 데이터를 받아 수정을 하는 put 메서드에서 많이 사용함
from rest_framework.response import Response
from rest_framework.views import APIView

class PostListCreateAPIView(APIView):
	def get(self, request):
		serializer = PostSerializer(Post.objects.all(), many=True)
        #instance keyword사용시 아래와 같이 쓸 수도 있다
        #serializer = PostSerializer(instance=Post.objects.all(), many=True)
        
		return Response(serializer.data)
        
	def post(self, request):
    	#data키워드 사용
		serializer = PostSerializer(data=request.data) #이 순간에는 유효성 검사가 진행된 게 아님
		if serializer.is_valid():#이 순간에 유효성 검사가 진행
			serializer.save()#유효성 검사가 성공할 경우 저장
			return Response(serializer.data, status=201)
		return Response(serializer.errors, status=400)#유효성 검사에 통과하지 못한 경우

 

  • 추가)함수형식의 뷰(FBV)에서 사용되는 시리얼라이저 예시
  • 사실상 위의 클래스기반 형식의 뷰와 유사하다.
def post_list_or_create(request):
	if request.method == 'POST':
		serializer = PostSerializer(data=request.POST)
		if serializer.is_valid():
			serializer.save()
			return Response(serializer.data, status=201)
		return Response(serializer.errors, status=400)
	else:
        qs = Post.objects.all()
        serializer = PostSerializer(qs, many=True)
        return Response(serializer.data)

 

'django > drf' 카테고리의 다른 글

직렬화(노션으로 이동완료)  (0) 2022.05.23

BELATED ARTICLES

more