[DRF] Serializer CRUD - DRF TUTORIALS
django에는 form으로 html에 데이터를 요청 응답하거나 api로 프론트엔드와 request와 response를 한다.
나는 요즘 프론트엔드 친구와 장고와 리액트로 협업을 진행하면서 api는 처음 써보기에 정리를 하고자 한다.
계속 해서 Serializer의 CRUD와 View에서의 CRUD가 헷갈렸는데 애초에 CRUD는 데이터를 처리하는 과정(데이터 베이스)에서의 용어이다.
Serializer는 model(데이터 베이스, DB)의 데이터의 유효성을 검증하고, model(DB)의 데이터의 변환(JSON, 다른 형식), 처리 그리고 클라이언트로부터 받은 데이터를 모델(DB)에 저장하거나, 모델(DB)의 데이터를 수정하는 것을 담당하기에 Serializer에서 CRUD는 당연히 정의가 되어야 하며,
View는 HTTP의 POST, GET, PUT, PETCH, DELETE와 같이 주요 HTTP메서드를 처리하게 되는데 HTTP를 처리하는 방식이 CRUD 비슷하기에 헷갈렸던 것 같다. 그렇기에 view에서는 CRUD라는 말은 어울리지 않는 말이다. 하지만 그렇다고 못하는 것은 아니다!!!
그래서 헷갈렸다. 이제는 헷갈릴 필요가 없다. 이제야 이해가 되었다!!!!!
@@++@@++@@
Serializer는 기본적으로 model의 데이터의 유효성을 검사하고, model과 데이터의 변환을 하며 처리한다.
그렇기에 Serializer의 CRUD기능이 들어가며, CRUD는 대부분의 컴퓨터 소프트웨어가 가지는 기본적인 데이터 처리 기능인 Create(생성), Read(읽기), Update(갱신), Delete(삭제)를 묶어서 일컫는 말이다.
사용자 인터페이스가 갖추어야 할 기능(정보의 참조/검색/갱신)을 가리키는 용어로서도 사용된다.
Serializer는 Django Form과 컨셉/사용법이 유사하다. 하지만 생성자를 지정할 때, 인자 구성이 조금 다르다. Django Form의 생성자는 첫번째 인자로 data를 받으며 ModelForm에서는 instance 인자가 추가로 지정되어 있다.
반면, Serializer의 생성자는 아래 코드와 같이 첫번째 인자로 instance를 받으며, 두번째 인자로 data를 받는다.
# rest_framework/serializers.py
class BaseSerializer(Field):
def __init__(self, instance=None, data=empty, **kwargs):
# 생략
class Serializer(BaseSeializer):
# 생략
이렇기 때문에 data 인자만 지정할 때에는 아래 코드와 같이 필수적으로 keyword를 지정해주어야 한다.
serializer = PostSerializer(post)
serializer = PostSerializer(data=request.data)
serializer = PostSerializer(post, data=reqeust.data)
serializer = PostSerializer(post, reqeust.data)
serializer = PostSerializer(reqeust.data) # 오류
이 코드가 왜 그런지는 밑에서 차차 설명을 하도록 하겠다.
Serializer에서 CRUD기능을 추가 해줄 수 있느데 코드를 한번 보자.
DRF에 공식문서에 있는 튜토리얼을 가지고 진행을 해보았다.
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted([(item, item) for item in get_all_styles()])
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
class Meta:
ordering = ['created']
일단 Snippet이라는 모델을 정의해 줬다.
django는 models에 AbstractUser와 AbstractBaseUser가 있는데 이것은 장고 내장 models이며 기본적으로 지원해주는 field들이 있다. 그렇기에 위에 처럼 커스텀 유저로 만들어서 따로 정의해주지 않아도 기본적으로 내장되어있는 필드들이 존재 하는데 여기서는 커스텀 유저로 만들기 위해 models.Model를 가지고 왔다.
models라는 클래스를 상속한 Snippet이라는 모델을 만들어주었고 models라는 클래스 안에 Model이라는 속성을 받아서 각 필드에 대한 정의를 해주었다.
class Meta:
ordering = ['created']
위 코드는 snippet 모델을 created 순서대로 모델을 사용하겠다는 의미이다.
모델을 정의한 후 마이그레이션을 진행한다.
이제 시리얼라이저를 정의해줘야 하는데 시리얼라이저에는 serializer라는 속성이 있고, modelserializer라는 속성이 있는데 serializer라는 속성은 우리가 serializer를 만들 때 입력받을 필드를 모두 정의해줘야 한다. 그리고 modelserializer는 이미 모델에 정의한 필드를 그대로 가지고 오겠다는 속성을 갖는다. 이것은 코드로 살펴보자.
Serializer로 생성
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
code = serializers.CharField(style={'base_template': 'textarea.html'})
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
Modelserializer로 생성
class SnippetSerializer(serializers.ModelSerializer):
class Meta:
model = Snippet
fields = ['id', 'title', 'code', 'linenos', 'language', 'style']
Modelserializer로 아주 간단하게 끝나는 것을 볼 수 있다.
Serializer를 간단하게 설명을 하자면 Serializer란 말 그대로 직렬화하는 클래스로서, 사용자의 DB안에 사용자 프로필 사진, 이메일, 이름, 성별이 있다고 가정하면 사용자 모델 인스턴스를 JSON 형태 혹은 Dictionary 형태로 직렬화 할 수 있다.
그렇기에 우리는 Serializer를 가지고 우리가 가지고 있는 파이썬에서 db데이터 형태인 queryset의 형태를 JSON이나 DICTIONART형태로 바꿔주는 역할을 한다. 그렇기에 model을 우리가 정의해도 그 model을 get 하거나 post할 때 JSON으로 바꿔주는 변환을 해주어야 한다. 그렇기에 Serializer를 따로 또 정의해주는 것이다.
이제 CRUD에서 c와 u에 해당하는 create와 update의 함수를 넣어보자.
이코드는 serializer.py 파일 밑에 serializer의 클래스를 정의한 밑에다가 써주게 된다.
class SnippetSerializer(serializers.Serializer):
# 생략 #
def create(self, validated_data):
"""
Create and return a new `Snippet` instance, given the validated data.
"""
return Snippet.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `Snippet` instance, given the validated data.
"""
instance.title = validated_data.get('title', instance.title)
instance.code = validated_data.get('code', instance.code)
instance.linenos = validated_data.get('linenos', instance.linenos)
instance.language = validated_data.get('language', instance.language)
instance.style = validated_data.get('style', instance.style)
instance.save()
return instance
validated_data는 요청이나 응답을 보낼 때 들어오는 데이터가 유효한지에 대해 검증이 끝나고 저장되는 데이터로 다음 블로그에서 validatation(유효성 검증)에 대해서 정리를 할 예정이다. 단순히 이것이 우리가 정해준 필드에 맞게 들어왔나를 확인하는것이고며 유효성검증을 통과하면 validated_data에 저장을 하고, 만약 유효하지 않다면 서버에 에러를 띄우고 에러데이터를 담게 된다.
여기서 이제 instance의 개념이 나오게 된다.
파이썬에서의 클래스와 instance, object의 개념을 살짝 설명을 한다면 예시를 들면은 가장 대중적으로 드는 예시가 붕어빵이다.
붕어빵을 만들기 위해서는 붕어빵 틀이 필요하고 그리고 들어가는 재료가 필요하다. 그리고 그 틀에 의해서 붕어빵이 만들어지는데 클래스는 붕어빵을 만들기 위한 틀이 되며, 붕어빵의 들어가는 재료는 클래스의 속성이 된다. 그리고 붕어빵 틀에 의해 만들어진 붕어빵이 object(객체)가 되고 붕어빵틀에 의해 만들어진 붕어빵을 붕어빵틀에 instance라고 한다.
클래스
객체를 만들어 내기 위한 틀이며 만들어 낼 객체의 속성과 메서드의 집합을 담아놓은 것
객체
클래스로부터 만들어지는 실체, 클래스로 선언된 변수를 객체
인스턴스
객체가 메모리에 할당이 된 상태이며 런타임에 구동되는 객체를 말한다. 객체와 같은 의미로 쓰이기도 한다.
class Person:
def __init__(self,name):
self.name = name
a= Person(ray)
print(a.name)
# ray
여기서 클래스는 Person이 되고 객체는 a가 되고 클래스 Person에 인스턴스를 a라고 한다. 여기서 객체와 instance가 헷갈릴 수 있는데
점프 투 파이썬 이란 책에서는 인스터스와 객체의 차이를 밑에와 같이 정의를 했다.
클래스로 만든 객체를 ‘인스턴스’라고도 한다. 그렇다면 객체와 인스턴스의 차이는 무엇일까? 이렇게 생각해 보자. a = Cookie()로 만든 a는 객체이다. 그리고 a 객체는 Cookie의 인스턴스이다. 즉, 인스턴스라는 말은 특정 객체(a)가 어떤 클래스(Cookie)의 객체인지를 관계 위주로 설명할 때 사용한다. ‘a는 인스턴스’보다 ‘a는 객체’라는 표현이 어울리며 ‘a는 Cookie의 객체’보다 ‘a는 Cookie의 인스턴스’라는 표현이 훨씬 잘 어울린다.
이것에 대한 생성자나 여러가지 설명할 것이 많은데 이것 또한 블로그에 올려야지!!
계속 Serializer를 진행해보자.
위에 create와 update 코드를 보면 update에는 인스턴스에 대한 내용이 있는데 create에는 없는 것을 볼 수 있다.
과연 왜 그럴까 나또한 생각을 많이 했는데 굉장히 간단한 문제였다.
인스턴스는 위에 의미를 봤을 때 클래스에 의해 만들어진 객체가 된다.
그렇기에 create에는 존재하지 않는다 왜냐하면 새로운 객체를 만들어서 db에 저장을 하기 때문이다. 즉 클라이언트로부터 전달받은 데이터를 유효성 검증을 하고, 모델 인스턴스를 생성하여 db에 저장을 한다.
하지만 update는 기존의 객체를 수정하는 역할을 한다. 원래 저장되어있던 db를 들고와서 클래스의 인스턴스를 가지고 수정하고 바꿔준다음에 다시 저장하는 형태이기에 인스턴스를 가지고 와야 하는 것이다. 즉 클라이언트로부터 전달받은 데이터를 유효성 검증을 하고, 모델 인스턴스를 업데이트 한다.
@@
그렇기에 Serializer는 첫번째 인자로 instance를 받게 되고, 두번 째로 data를 받게 되는 것이다.
그리고 맨 위에서 data만 인자를 받기 위해서 우리가 코드를 써놓은 것처럼 CRUD에서 Create이다. 그렇기에 instance를 받지 않으며 데이터만 받기에 read방식으로 보내고 그에 대한 request.data를 보낸다는 코드로 작성이 되어야 한다.==> 맨 위 코드의 설명!!!
이제 Serializer를 만들어 줬기에 그것이 하는 역할을 보자.
위에 이미지를 보면 알듯이 serializer는 파이썬에서 model의 데이터 형태인 queryset을 json으로 변환하기 전 단계인 OrderedDict로 바꿔주는 역할을 하며 그 반대로 OrderedDict를 queryset데이터로 역변환도 시켜준다.
즉 우리가 get, post 또는 Create, Read, Update, Delete에 기능을 할 때 꼭 Serializer를 사용해야 한다.
파이썬 shell을 켜고 한번 돌려보자
python manage.py shell
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
snippet = Snippet(code='foo = "bar"\n')
snippet.save()
snippet = Snippet(code='print("hello, world")\n')
snippet.save()
serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
Snippet을 snippet이라는 인스턴스로 만들고 그것을 SnippetSerializer로 만들어 준다음 serializer.data를 뽑아 보았다.
여기서 모두 code라는 필드에 대한 값만을 주었기에 serializer.data에서는 자동으로 생성되는 id와 code에 해당하는 값만이 들어가 있는것을 볼 수 있다.
여기서 이제 Get 방식과 Post방식으로 나뉜다. Serializer는 위에 그림과 설명으로 했듯이 파이썬의 queryset 형식을 OrderedDict로 바꿔주는 직렬화 아니면 그 반대로 OrderDict를 querySet으로 바꿔주는 역직렬화의 역할을 한다. 이제 이것을 JSON으로 내보내기 위해서는 DRF에서 지원하는
OrderDict형태를 JSON으로 바꿔주는 JSONRenderer()
JSON 형태를 OrderDict 로 바꿔주는 JSONParser()
를 이용해야한다. 코드로 보자.
JSONRenderer()
# OrderDict형태를 JSON으로 바꿔주는 JSONRenderer()
content = JSONRenderer().render(serializer.data)
content
# b'{"id": 2, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly"}'
JSONParser()
# JSON형태를 OrderDict로 바꿔주는 JSONParser()
import io
stream = io.BytesIO(content)
data = JSONParser().parse(stream)
이제 여기서 get방식은 사용자가 서버에 요구하는 것이기에 JSON형태로 내보내기만 하면 된다.
하지만 POST방식은 회원가입이나 게시물 생성과 같은 새로운 데이터를 서버에 보내서 저장하는 것이기에 JSON형태를 OrderDict로 바꿔준 다음에 Serializer을 이용해서 ORderdict형태를 queryset으로 바꾼후 db에 저장하는 순서로 넘어가야 한다.
serializer = SnippetSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>
위에 코드가 이제 db에 저장을 하는 코드가 되는 것이다. 우리가 위에서 봤듯이 Serializer는 data만 인자로 받기 위해서는 data라고 꼭 명시를 해줘야 하고 (아니면 (post, data)이런식으로 써야한다.) 그 다음에 들어온 데이터에 대해서 유효성 검증을 진행 후 문제가 없다면 유효성 검증이 끝난 데이터 들이 들어가 있는 serializer.validated_data를 한번 확인한후 save() 메서드로 db에 저장하게 된다.
우리는 지금까지 SnippetSerializer라는 클래스가 호출이 될때 어떠한 함수가 작동하고 serializer가 어떤 역할을 하는지에 대해 알아보았다.
이제 view로 넘어가야 하는데 view 또한 혼자서 열심히 공부하면서 이해하려고 하지만 이 부분도 굉장히 어렵다.
하지만 시간이 남을 때 이 부분 또한 모두 정리할 것이다.
@@++@@