Serializers

serializers λŠ” querysetκ³Ό λͺ¨λΈ μΈμŠ€ν„΄μŠ€μ™€ 같은 λ³΅μž‘ν•œ 데이터λ₯Ό JSON, XML λ˜λŠ” λ‹€λ₯Έ 컨텐츠 μœ ν˜•μœΌλ‘œ μ‰½κ²Œ λ Œλ”λ§ν•  수 μžˆλŠ” python κΈ°λ³Έ 데이터 μœ ν˜•μœΌλ‘œ λ³€ν™˜ν•΄μ€€λ‹€. λ˜ν•œ deserialization 을 μ œκ³΅ν•˜μ—¬, λ“€μ–΄μ˜€λŠ” λ°μ΄ν„°μ˜ μœ νš¨μ„±μ„ 처음 ν™•μΈν•œ 후에 ꡬ문 뢄석이 된 데이터λ₯Ό 볡합 ν˜•μ‹μœΌλ‘œ λ‹€μ‹œ λ³€ν™˜ν•  수 μžˆλ‹€.

REST ν”„λ ˆμž„μ›Œν¬μ˜ serializers λŠ” Django의 Form, modelForm ν΄λž˜μŠ€μ™€ 맀우 μœ μ‚¬ν•˜κ²Œ μž‘λ™ν•œλ‹€. serializers λŠ” ModelSerializer, Serializer 클래슀λ₯Ό μ œκ³΅ν•œλ‹€.

μ‚¬μš©μ „μ— djangorestframework κ°€ μ„€μΉ˜λ˜μ–΄μžˆλŠ”μ§€ ν™•μΈν•΄μ•Όν•œλ‹€.

$ pip list
Package             Version
------------------- -------
Django              2.1.7  
djangorestframework 3.9.2  
pip                 19.0.3 
pytz                2018.9 
setuptools          40.6.2

Serialzers μ„ μ–Έ

κ°„λ‹¨ν•œ 예λ₯Όλ“€κΈ° μœ„ν•΄μ„œ 객체 ν•œκ°œλ₯Ό μƒμ„±ν•œλ‹€.

# models.py
from django.db import models
from datetime import datetime

# Create your models here.
class Comment(models.Model):
    email = models.CharField(max_length=100,blank=False)
    content = models.TextField()
    created_at = models.DateTimeField(default=datetime.now)
$ ./manage.py shell
>>> from quickstart.models import Comment
>>> from datetime import datetime
>>> c=Comment(email="test@test.com", content="testsetstsetset")
>>> c.save()
>>> Comment.objects.all()
<QuerySet [<Comment: Comment object (1)>, <Comment: Comment object (2)>]>
>>>

comment 객체에 ν•΄λ‹Ήν•˜λŠ” 데이터λ₯Ό serializer, deserializerν™” ν•  수 μžˆλŠ” serializerλ₯Ό μ„ μ–Έν•œλ‹€.

# appν”„λ‘œμ νŠΈμ— serializers.py μΆ”κ°€
from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

Serializing Objects

μƒμ„±ν•œ Serializer 클래슀λ₯Ό μ‚¬μš©ν•˜μ—¬ ν•΄λ‹Ή 객체λ₯Ό serializingν•  수 μžˆλ‹€.

>>> from quickstart.serializers import CommentSerializer
>>> c = Comment.objects.get(id=1)
>>> serializer = CommentSerializer(c)
>>> serializer.data
{'email': 'test@test.com', 'content': 'testsetstsetset', 'created_at': '2019-10-08T07:28:15.082464Z'}

이 μ‹œμ μ—μ„œλŠ” λͺ¨λΈ μΈμŠ€ν„΄μŠ€λ₯Ό 파이썬 κΈ°λ³Έ 데이터 μœ ν˜•μœΌλ‘œ λ³€ν™˜ν•œ 것이닀.

JSON λ°μ΄ν„°λ‘œ λ³€ν™˜ν•˜κΈ° μœ„ν•΄μ„œλŠ” JSONRendererλ₯Ό μ΄μš©ν•΄ json으둜 λ°”κΏ”μ€€λ‹€.

>>> from rest_framework.renderers import JSONRenderer
>>> json = JSONRenderer().render(serializer.data)
>>> json
b'{"email":"test@test.com","content":"testsetstsetset","created_at":"2019-10-08T07:28:15.082464Z"}'

Deserializing Objects

serializingκ³Ό μœ μ‚¬ν•˜λ‹€. μš°μ„  json데이터λ₯Ό 파이썬 데이터 ν˜•μ‹μœΌλ‘œ parsingν•œ 후에 κΈ°λ³Έ 데이터λ₯Ό κ²€μ¦λœ λ°μ΄ν„°λ‘œ λ³΅μ›ν•œλ‹€.

>>> from django.utils.six import BytesIO
>>> from rest_framework.parsers import JSONParser
>>> stream = BytesIO(json)
>>> data = JSONParser().parse(stream)
>>> data
{'email': 'test@test.com', 'content': 'testsetstsetset', 'created_at': '2019-10-08T07:28:15.082464Z'}
>>> serializer = CommentSerializer(data=data)
>>> serializer.is_valid()
True
>>> serializer.validated_data
OrderedDict([('email', 'test@test.com'), ('content', 'testsetstsetset'), ('created_at', datetime.datetime(2019, 10, 8, 7, 28, 15, 82464, tzinfo=<UTC>))])

Return Instance

μ™„μ „ν•œ 객체 μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•˜κΈ° μœ„ν•΄μ„œλŠ” create(), update() λ©”μ†Œλ“œλ₯Ό κ΅¬ν˜„ν•΄μ„œ object instance λ°˜ν™˜μ΄ κ°€λŠ₯ν•˜λ‹€.

from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

    def create(self, validated_data):
      return Comment(**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 = validated_data.get('created', instance.created)
      return instance

객체 μΈμŠ€ν„΄μŠ€κ°€ λͺ¨λΈκ³Ό μΌμΉ˜ν•˜λŠ” κ²½μš°μ—λŠ” μ•„λž˜μ™€ 같이 객체λ₯Ό λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯λ˜λ„λ‘ ν•΄μ•Όν•œλ‹€.

from rest_framework import serializers

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

    # λͺ¨λΈ 객체와 μΌμΉ˜ν•˜λŠ” 경우
      def create(self, validated_data):
        return Comment.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 = validated_data.get('created', instance.created)
        instance.save()
        return instance

λ°μ΄ν„°λŠ” deserializingν•  λ•Œ .save()λ₯Ό ν˜ΈμΆœν•˜μ—¬ μœ νš¨μ„±μ΄ κ²€μ‚¬λœ 데이터λ₯Ό 기반으둜 객체 μΈμŠ€ν„΄μŠ€λ₯Ό λ°˜ν™˜ν•  수 μžˆλ‹€.

>>> deserializer = CommentSerializer(data=serializer.data)
>>> comment = deserializer.save()
>>> comment
<Comment: Comment object (3)>

μ—¬κΈ°μ„œ save()λŠ” instanceκ°€ μ‘΄μž¬ν•˜λ©΄ updateλ₯Ό μ‘΄μž¬ν•˜μ§€μ•ŠμœΌλ©΄ createλ₯Ό ν•΄μ€€λ‹€.

# .save() will create a new instance.
serializer = CommentSerializer(data=data)

# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)

μΈμŠ€ν„΄μŠ€λ₯Ό μ €μž₯ν•˜λŠ” μ‹œμ μ— λ·° μ½”λ“œκ°€ 데이터λ₯Ό μΆ”κ°€ν•  수 μžˆμ–΄μ•Όν•˜λ©°, 이 μΆ”κ°€ λ°μ΄ν„°μ—λŠ” λ‹€λ₯Έ 정보가 포함될 수 μžˆλ‹€.

>>> serializer.save(writer=request.user)

μΆ”κ°€λœ λ°μ΄ν„°λŠ” .create() ν˜Ήμ€ .update()κ°€ 호좜될 λ•Œ validated_data에 ν¬ν•¨λœλ‹€.

Validation

데이터λ₯Ό deserializerν•  λ•Œ μœ νš¨μ„±μ΄ κ²€μ¦λœ 데이터에 μ ‘κ·Όν•˜κΈ° 전에 is_valid() λ₯Ό ν˜ΈμΆœν•˜κ±°λ‚˜ 객체 μΈμŠ€ν„΄μŠ€λ₯Ό μ €μž₯ν•΄μ•Όν•œλ‹€.

>>> deserializer = CommentSerializer(data={'email':'admin@test.com', 'content':'This is test'})
>>> deserializer.save()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/jeongdaye/.pyenv/versions/restful/lib/python3.7/site-packages/rest_framework/serializers.py", line 179, in save
    'You must call `.is_valid()` before calling `.save()`.'
AssertionError: You must call `.is_valid()` before calling `.save()`.
>>> deserializer.is_valid()
False
>>> deserializer.errors
{'created_at': [ErrorDetail(string='This field is required.', code='required')]}

λ§Œμ•½μ— is_valid()κ°€ False라면, .errors()λ₯Ό ν†΅ν•΄μ„œ κ²°κ³Ό 였λ₯˜λ©”μ„Έμ§€λ₯Ό 확인할 수 μžˆλ‹€.

>>> deserializer.is_valid(raise_exception=True)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/jeongdaye/.pyenv/versions/restful/lib/python3.7/site-packages/rest_framework/serializers.py", line 243, in is_valid
    raise ValidationError(self.errors)
rest_framework.exceptions.ValidationError: {'created_at': [ErrorDetail(string='This field is required.', code='required')]}

raise_exception=True ν”Œλž˜κ·Έλ₯Ό μ‚¬μš©ν•΄ serializers.validationError μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚¬ 수 μžˆλ‹€.

Field 레벨 검증

validate_field

serializer μ„œλΈŒ ν΄λž˜μŠ€μ— .validate_<field_name> λ©”μ†Œλ“œλ₯Ό μΆ”κ°€ν•΄ custom field μœ νš¨μ„± 검증을 μ§€μ •ν•  수 μžˆλ‹€.

import re 
emailRegex = '^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$'

class CommentSerializer(serializers.Serializer):
    email = serializers.CharField()
    content = serializers.CharField()
    created_at = serializers.DateTimeField()

    def validate_email(self, value):
        if(re.search(eamilRegex, value)):
            return value
        else:
            raise serializers.ValidationError("이메일 ν˜•μ‹μ΄ 잘λͺ»λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")
>>> from quickstart.serializers import CommentSerializer
>>> from datetime import datetime
>>> serializer = CommentSerializer(data={'email':'asdfsdaf', 'content':'asdfasdfasdfasf', 'created_at': datetime.now()})
>>> serializer.is_valid()
False
>>> serializer.errors
{'email': [ErrorDetail(string='이메일 ν˜•μ‹μ΄ 잘λͺ»λ˜μ—ˆμŠ΅λ‹ˆλ‹€.', code='invalid')]}

validate

μ—¬λŸ¬ ν•„λ“œμ— λŒ€ν•œ μœ νš¨μ„± 검사λ₯Ό ν•˜λ €λ©΄ .validate() λ©”μ„œλ“œλ₯Ό μΆ”κ°€ν•˜λ©΄λœλ‹€. 이 λ©”μ„œλ“œλŠ” ν•„λ“œκ°’μ˜ dict μžλ£Œν˜•μ„ 단일 인수둜 μ·¨ν•œλ‹€.

class CommentSerializer(serializers.Serializer):
    email = serializers.CharField()
    content = serializers.CharField()
    created_at = serializers.DateTimeField()

    def validate(self, data):
        if (len(data['content']) < 10) & (data['created_at'] < timezone.now()):
            raise serializers.ValidationError("error")
        return data
>>> from quickstart.serializers import CommentSerializer
>>> from datetime import datetime
>>> serializer = CommentSerializer(data={'email':'asdfsdaf', 'content':'asdfasdfasdfasf', 'created_at': datetime.now()})
>>> serializer.is_valid()
True
>>> serializer = CommentSerializer(data={'email':'test@test.com', 'content':'asf', 'created_at': datetime.now()})
>>> serializer.is_valid()
False
>>> serializer.errors
{'non_field_errors': [ErrorDetail(string='error', code='invalid')]}

validators

Serializer의 κ°œλ³„ν•„λ“œμ— validatorsλ₯Ό 톡해 μœ νš¨μ„± 검사λ₯Ό ν•  수 μžˆλ‹€.

from rest_framework import serializers
import re

emailRegex = '^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$'

def email_check(value):
    if(re.search(emailRegex, value)):
        return value
    else:
        raise serializers.ValidationError("이메일 ν˜•μ‹μ΄ 잘λͺ»λ˜μ—ˆμŠ΅λ‹ˆλ‹€.")

class CommentSerializer(serializers.Serializer):
    email = serializers.CharField(validators=[email_check])
    content = serializers.CharField()
    created_at = serializers.DateTimeField()
>>> from quickstart.serializers import CommentSerializer
>>> from datetime import datetime
>>> serializer = CommentSerializer(data={'email':'asdfsdaf', 'content':'asdfasdfasdfasf', 'created_at': datetime.now()})
>>> serializer.is_valid()
False
>>> serializer.errors
{'email': [ErrorDetail(string='이메일 ν˜•μ‹μ΄ 잘λͺ»λ˜μ—ˆμŠ΅λ‹ˆλ‹€.', code='invalid')]}

ModelSerializer

ModelSerializer ν΄λž˜μŠ€λŠ” λͺ¨λΈ ν•„λ“œκ°€ ν¬ν•¨λœ Serailzer 클래슀λ₯Ό κ°„λ‹¨ν•˜κ²Œ λ§Œλ“€ 수 μžˆλ‹€.

  • λͺ¨λΈμ„ 기반으둜 일련의 ν•„λ“œκ°€ μžλ™μœΌλ‘œ 생성

  • unique_together validator 와 같은 serializer에 λŒ€ν•œ validatorλ₯Ό μžλ™ 생성

  • .create()와 .update()의 κ°„λ‹¨ν•œ κΈ°λ³Έ κ΅¬ν˜„μ„ 포함

from rest_framework import serializers

class CommentSerializer(serializers.ModelSerializer):
  class Meta:
      model = Comment
      fields = ('email','content','created_at')

λ§Œμ•½ 전체 ν•„λ“œλ₯Ό ν¬ν•¨ν•˜κ³  μ‹Άλ‹€λ©΄ __all__ 으둜 속성을 μ„€μ •ν•˜λ©΄λœλ‹€.

from rest_framework import serializers

class CommentSerializer(serializers.ModelSerializer):
  class Meta:
      model = Comment
      fields = '__all__'
>>> from quickstart.serializers import CommentSerializer
>>> serializer = CommentSerializer()
>>> print(repr(serializer))
CommentSerializer():
    id = IntegerField(label='ID', read_only=True)
    email = CharField(max_length=100)
    content = CharField(style={'base_template': 'textarea.html'})
    created_at = DateTimeField(required=False)

exclude μ†μ„±μœΌλ‘œ μ œμ™Έν•˜κ³  싢은 ν•„λ“œλ§Œ μ„€μ •ν•  μˆ˜λ„ μžˆλ‹€.

from rest_framework import serializers

class CommentSerializer(serializers.ModelSerializer):
  class Meta:
      model = Comment
      exclude = ('email','content','created')

HyperlinkedModelSerializer

HyperlinkedModelSerializer ν΄λž˜μŠ€λŠ” κΈ°λ³Έ ν‚€κ°€ μ•„λ‹Œ 관계λ₯Ό λ‚˜νƒ€λ‚΄κΈ° μœ„ν•΄ ν•˜μ΄νΌλ§ν¬λ₯Ό μ‚¬μš©ν•œλ‹€λŠ” 점을 μ œμ™Έν•˜κ³ λŠ” ModelSerialzer ν΄λž˜μŠ€μ™€ μœ μ‚¬ν•˜λ‹€. 기본적으둜 κΈ°λ³Έ ν‚€ ν•„λ“œ λŒ€μ‹  url ν•„λ“œκ°€ ν¬ν•¨λœλ‹€.

class CommentSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Comment
        fields = ('url','id','email', 'content', 'created_at')
>>> from quickstart.serializers import CommentSerializer
>>> serializer = CommentSerializer()
>>> print(repr(serializer))
CommentSerializer():
    url = HyperlinkedIdentityField(view_name='comment-detail')
    id = IntegerField(label='ID', read_only=True)
    email = CharField(max_length=100)
    content = CharField(style={'base_template': 'textarea.html'})
    created_at = DateTimeField(required=False)

url fieldλŠ” HyperlinkedIdentityFieldλ₯Ό μ‚¬μš©ν•œλ‹€.

HyperlinkedModelSerializer 클래슀λ₯Ό μΈμŠ€ν„΄μŠ€ν™” ν• λ•ŒλŠ” ν˜„μž¬ request λ₯Ό serializer μ»¨ν…μŠ€νŠΈμ— ν¬ν•¨ν•΄μ•Όν•œλ‹€.

serializer = AccountSerializer(queryset, context={'request': request})

μ΄λ ‡κ²Œ μ „λ‹¬ν•˜λ©΄ μ •κ·œν™”λœ URL을 μ‚¬μš©ν•œλ‹€.

http://api.example.com/accounts/1/

λ§Œμ•½ μƒλŒ€ URL을 μ‚¬μš©ν•˜λ €λ©΄ {'request': None} 을 λͺ…μ‹œμ μœΌλ‘œ μ „λ‹¬ν•˜λ©΄λœλ‹€.

/accounts/1

How hyperlinked views are determined

λͺ¨λΈμΈμŠ€ν„΄μŠ€μ— ν•˜μ΄νΌλ§ν¬ν•˜κΈ° μœ„ν•΄ μ–΄λ–€ λ·°λ₯Ό μ‚¬μš©ν–ˆλŠ”μ§€ μ•Œλ €μ€˜μ•Όν•œλ‹€. 기본적으둜 {model_name} -detail μŠ€νƒ€μΌκ³Ό view 이름이 μΌμΉ˜ν•΄μ•Όν•˜λ©°, pk ν‚€μ›Œλ“œ 인수둜 μΈμŠ€ν„΄μŠ€λ₯Ό μ°ΎλŠ”λ‹€. λ§Œμ•½ λ”°λ‘œ μ§€μ •ν•΄μ£Όκ³  μ‹Άλ‹€λ©΄ extra_kwargs μ„€μ •μ—μ„œ view_name λ˜λŠ” lookup_field μ˜΅μ…˜μ„ μ„€μ •ν•˜λ©΄λœλ‹€.

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Account
        fields = ('account_url', 'account_name', 'users', 'created')
        extra_kwargs = {
            'url': {'view_name': 'accounts', 'lookup_field': 'account_name'},
            'users': {'lookup_field': 'username'}
        }

λ˜λŠ” serializerμ—μ„œ ν•„λ“œλ₯Ό λͺ…μ‹œμ μœΌλ‘œ μ„€μ •ν•  수 μžˆλ‹€.

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(
        view_name='accounts',
        lookup_field='slug'
    )
    users = serializers.HyperlinkedRelatedField(
        view_name='user-detail',
        lookup_field='username',
        many=True,
        read_only=True
    )

    class Meta:
        model = Account
        fields = ('url', 'account_name', 'users', 'created')

참고링크

Last updated

Was this helpful?