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