All Articles

Django Rest Framework 활용하기 1 - Serializer

Udemy 강의를 들으면서, DRF 공식문서를 보면서, 그리고 구글링하면서 정리한 내용입니다.

Django REST Framework - Level One

Introduction to DRF and NewsAPI Project Setup

DRF 초기 세팅

  1. pip install djangorestframework
  2. 프로젝트 폴더 settings.py 안 INSTALLED APPS 항목에 rest_framework 추가
  3. models.py 작성
  4. python manage.py makemigrations, python manage.py migrate
  5. python manage.py createsuperuser
  6. 만든 모델 클래스를 admin.py에 등록

    from news.models import 모델클래스이름
    admin.site.register(모델클래스이름)
  7. python manage.py runserver 포트번호로 서버가 잘 띄워지는지, admin 페이지 잘 생성되었나 확인

What are the Serializers?

쿼리셋이나 모델 인스턴스 같은 복잡한 데이터를 파이썬 데이터 타입으로 변환한 뒤 Json 형태로 바꿔주는 과정을 data serialization이라 한다. 시리얼라이저에는 여러 형태가 있지만 Serializer나 ModelSerializer 클래스가 사용하기 좋다. 시리얼라이저에는 deserialization 기능도 있는데, 파싱한 데이터를 반대로 복잡한 타입으로 바꾸는 작업이다.

시리얼라이저는 필드 형태와 관계 없이 모델 인스턴스를 Json 형태 혹은 dictionary 형태로 자동 변환해준다.

다음과 같은 사용자 모델이 있고 사용자 프로필 페이지에 접근했을 때 띄워줄 view를 짠다고 하면 해당하는 사용자 id(Primary key) 번호만 URL에 입력하기만 하면 사용자 정보를 Json 형태로 리턴할 수 있다. 매우 간편!

user = User(
  email = "user@user.user",
  name = "user",
  sex = "Female",
  profile_image = "user.png"
)

UserSerializer(user).data{
	"email" : "user@user.user",
  "name" : "user",
  "sex" : "female",
  "profile_image" : "user.png"
}

serializers.py 예시

from rest_framework import serializers
from news.models import Article

class ArticleSerializer(serializers.Serializer):
  # pk인 id는 99퍼센트 수정 안 할 것이므로 read_only
  id = serializers.IntegerField(read_only = True)
  author = serializers.CharField()
  title = serializers.CharField()
  description = serializers.CharField()
  body = serializers.CharField()
  location = serializers.CharField()
  publication_date = serializers.DateField()
  active = serializers.BooleanField()
  # 장고에서 자동으로 관리해주는 부분이므로 read_only
  created_at = serializers.DateTimeField(read_only = True)
  updated_at = serializers.DateTimeField(read_only = True)
    
  def create(self, validated_data):
    return Article.objects.create(**validated_data)

  def update(self, instance, validated_data):
    # id, created_at, updated_at은 read only 필드이므로 update method에서는 제외함
    # 'author'에 새로 들어오는 데이터가 없으면 이미 가지고 있는 instance.author를 사용함 (즉, 기존 데이터 유지)
    instance.author = validated_data.get('author', instance.author)
    instance.title = validated_data.get('title', instance.title)
    instance.description = validated_data.get('description', instance.description)
    instance.body = validated_data.get('body', instance.body)
    instance.location = validated_data.get('location', instance.location)
    instance.publication_date = validated_data.get('publication_date', instance.publication_date)
    instance.active = validated_data.get('active', instance.active)
    instance.save()
    return instance

위 시리얼라이즈를 이용해 python shell에서 데이터를 찍어보면 이렇게 나온다.

>>> article_instance = Article.objects.first()
>>> article_instance
<Article: John Doe My First Article>
>>> serializer = ArticleSerializer(article_instance)
>>> serializer
ArticleSerializer(<Article: John Doe My First Article>):
  id = IntegerField(read_only=True)
  author = CharField()
  title = CharField()
  description = CharField()
  body = CharField()
  location = CharField()
  publication_date = DateField()
  active = BooleanField()
  created_at = DateTimeField(read_only=True)
  updated_at = DateTimeField(read_only=True)
>>>

시리얼라이저에 인자로 준 인스턴스의 각 필드 속성이 출력된다. 여기에 .data를 이용해 Article 테이블의 첫 번째 인스턴스 데이터를 볼 수 있다. 첫 번째 인스턴스인 이유는 위에서 article_instance = Article.objects.first()로 선언했기 때문.

>>> serializer.data
{'id': 1, 'author': 'John Doe', 'title': 'My First Article', 'description': 'This is my first article.', 'body': 'This is the body of my first article.', 'location': 'Mapo', 'publication_date': '2020-07-20', 'active': True, 'created_at': '2020-07-24T01:22:17.269315Z', 'updated_at': '2020-07-25T08:26:28.581312Z'}

여기까지 모델 인스턴스를 파이썬 네이티브 데이터타입으로 변환하는 작업이다. 직렬화를 완료하기 위해서는 dictionary로 출력된 것을 json 형태로 변환해야 한다.

>>> from rest_framework.renderers import JSONRenderer
>>> json = JSONRenderer().render(serializer.data)
>>> json
b'{"id":1,"author":"John Doe","title":"My First Article","description":"This is my first article.","body":"This is the body of my first article.","location":"Mapo","publication_date":"2020-07-20","active":true,"created_at":"2020-07-24T01:22:17.269315Z","updated_at":"2020-07-25T08:26:28.581312Z"}'

별 차이 없어보이지만 "active"키 값(value)을 보면 차이점을 알 수 있다. Dictionary에서는 'active': True고 json 렌더링을 거친 결과물에서는 "active":true다.

하지만 type(json)을 찍어보면 <class 'bytes'>가 나온다. Article 테이블에 저장하기 위해서는 deserializing 작업을 통해 파이썬 네이티브 데이터로 바꿔줘야 하는데, 이때 사용하는 것이 JSONParser다.

>>> import io
>>> from rest_framework.parsers import JSONParser
>>> stream = io.BytesIO(json)
>>> data = JSONParser().parse(stream)
>>> data
{'id': 1, 'author': 'John Doe', 'title': 'My First Article', 'description': 'This is my first article.', 'body': 'This is the body of my first article.', 'location': 'Mapo', 'publication_date': '2020-07-20', 'active': True, 'created_at': '2020-07-24T01:22:17.269315Z', 'updated_at': '2020-07-25T08:26:28.581312Z'}

참고) io는 파이썬 모듈로, data streaming을 다루기 위한 인터페이스다.

위에서 변환한 파이썬 네이티브 데이터 타입을 validated data dictionary로 만들기 위해 ArticleSerializer를 다시 사용한다.

>>> serializer = ArticleSerializer(data = data)
>>> serializer.is_valid()
True
>>> serializer.validated_data
OrderedDict([('author', 'John Doe'), ('title', 'My First Article'), ('description', 'This is my first article.'), ('body', 'This is the body of my first article.'), ('location', 'Mapo'), ('publication_date', datetime.date(2020, 7, 20)), ('active', True)])

혹시 모를 에러에 대비해 .is_valid() 검증 과정은 필요하며, .is_valid()를 선언한 뒤에야 validated_data를 생성할 수 있다.

이렇게 검증한 데이터를 serializer에 저장해보자. 인스턴스가 존재하지 않으면 create, 존재하면 update를 한다.

>>> serializer.save()
{'author': 'John Doe', 'title': 'My First Article', 'description': 'This is my first article.', 'body': 'This is the body of my first article.', 'location': 'Mapo', 'publication_date': datetime.date(2020, 7, 20), 'active': True}
<Article: John Doe My First Article>

같은 내용이라 데이터가 추가된 것이 티가 안 나지만 Article 객체를 불러오면 2개가 출력된다.

>>> Article.objects.all()
<QuerySet [<Article: John Doe My First Article>, <Article: John Doe My First Article>]>

질문거리) 인스턴스가 존재했는데 왜 추가되었는가?

참고한 글