ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Django REST Framework] Serializers
    언어/파이썬 & 장고 2016. 7. 20. 15:09

    serializer는 queryset 과 모델 인스턴스와 같은 복잡한 데이터를 JSON, XML 또는 다른 콘텐츠 유형으로 쉽게 변환 할 수 있습니다. 또한 serializer는 받은 데이터의 유효성(validation)을 검사한 다음, 복잡한 타입으로 형변환을 할 수 있도록 serialization을 제공합니다. REST framework의 serializer는 Django의 형태와 ModelForm 클래스와 유사하게 동작합니다.

    Serializers

    Serializer 선언

    먼저 간단한 객체를 선언한 모습입니다.

    class Comment(object):
        def __init__(self, email, content, created=None):
            self.email = email
            self.content = content
            self.created = created or datetime.datetime.now()
    
    comment = Comment(email='leila@example.com', content='foo bar')


    다음 위에서 선언한 객체에 대한 serializer를 생성합니다.

    from rest_framework import serializers
    
    class CommentSerializer(serializers.Serializer):
        email = serializers.EmailField()
        content = serializers.CharField(max_length=200)
        created = serializers.DateTimeField()
    
        def restore_object(self, attrs, instance=None):
            """
            Given a dictionary of deserialized field values, either update
            an existing model instance, or create a new model instance.
            """
            if instance is not None:
                instance.email = attrs.get('email', instance.email)
                instance.content = attrs.get('content', instance.content)
                instance.created = attrs.get('created', instance.created)
                return instance
            return Comment(**attrs)

    시리얼 클래스의 첫 번째 부분은 serialized / deserialized를 얻을 필드를 정의합니다. restore_object 함수는 데이터를 deserialized 할 때 인스턴스가 생성되는 방법을 정의합니다.

    restore_object 함수는 선택 사항이며, 만약 우리가 사용할 serializer가  객체 인스턴스로 deserialized를 지원하려면 필요합니다. 함수를 정의하지 않는 경우, serializer 데이터는 dictionary 형태로 리턴합니다.

    Serializing objects

    위에서 정의한 CommentSerializer 클래스를 아래와 같이 사용할 수 있습니다.

    serializer = CommentSerializer(comment)
    serializer.data
    # {'email': u'leila@example.com', 'content': u'foo bar', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)}

    위는 모델 인스턴스를 파이썬 내부 데이터 타입으로 출력한 결과입니다. 


    JSON으로 출력을 하기 위해 아래와 같이 렌더링을 거칩니다.

    from rest_framework.renderers import JSONRenderer
    
    json = JSONRenderer().render(serializer.data)
    json
    # '{"email": "leila@example.com", "content": "foo bar", "created": "2012-08-22T16:20:09.822"}'

    Customizing field representation

    객체를 serializer로 변경할 때, 본인이 원하는 표현이 모델 내에서 이뤄지지 않을 수도 있습니다. 만약 특별한 필드를 커스터마이즈하기를 원한다면 transform_<fieldname>함수를 생성해 사용할 수 있습니다.

     

    description = serializers.CharField()
    description_html = serializers.CharField(source='description', read_only=True)
    
    def transform_description_html(self, obj, value):
        from django.contrib.markup.templatetags.markup import markdown
        return markdown(value)

    These methods are essentially the reverse of validate_<fieldname> (see Validation below.) - 이 방법은 validate_<filename> 함수의 기본적으로 반대입니다.

    Deserializing objects

    역직렬화의 방법은 간단합니다. 먼저 파이썬 native 데이터 유형으로 변환을 합니다.

    from StringIO import StringIO
    from rest_framework.parsers import JSONParser
    
    stream = StringIO(json) # json데이터
    data = JSONParser().parse(stream)


    다음  객체 인스턴스에 native 데이터 유형을 복원 할 수 있습니다.

    serializer = CommentSerializer(data=data)
    serializer.is_valid()
    # True
    serializer.object
    # <Comment object at 0x10633b2d0>


    데이터를 역직렬화할 때, 새로운 데이터를 생성하거나 이미 존재하는 데이터에 대해 수정할 수 있습니다.

    serializer = CommentSerializer(data=data)           # Create new instance
    serializer = CommentSerializer(comment, data=data)  # Update `comment`


    기본적으로 serializer는 사용자가 정의한 모든 필드에 대해 값을 체크해야 합니다. 만약 그렇지 않으면 validation 에러가 나게 됩니다. 정의한 전체 필드가 아닌 부분에 해당하는 필드 값만 업데이트를 하고 싶다면 partial이란 argument를 사용해야 합니다.

    serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)  # Update `comment` with partial data
    

    Validation

    데이터를 deserializing할 때, deserializing한 데이터를 사용하기 전에 항상 is_valid()함수를 호출해야 합니다. 만약 validation error가 발생한다면, .errors 속성을 사용해 에러 메세지를 확인할 수 있습니다.

    serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
    serializer.is_valid()
    # False
    serializer.errors
    # {'email': [u'Enter a valid e-mail address.'], 'created': [u'This field is required.']}

    위의 dictionary의 키는 serializer의 필드 이름이고 값은 해당 필드에 해당하는 에러메세지의 리스트입니다. 

    Field-level validation

    serial 클래스 내 validate_<필드 이름> 함수를 추가해 사용자 정의 필드 수준 유효성 검사를 지정할 수 있습니다. 해당 함수의 argument로 첫 번째 argument는 deserialized된 dictionary 타입을 받고, 두 번째는 validation을 검사할 dictionary의 키값을 받습니다.

    validate_<필드이름> 함수는 attrs이란 이름의 dictionary나 ValidationError 둘 중 하나를 리턴합니다.

    from rest_framework import serializers
    
    class BlogPostSerializer(serializers.Serializer):
        title = serializers.CharField(max_length=100)
        content = serializers.CharField()
    
        def validate_title(self, attrs, source):
            """
            Check that the blog post is about Django.
            """
            value = attrs[source]
            if "django" not in value.lower():
                raise serializers.ValidationError("Blog post is not about Django")
            return attrs

    Object-level validation

    여러 필드에 대한 접근을 요구로하는 validation 검사를 하기 위해선 시리얼 서브 클래스에 메서드 validate()를 추가합니다. 이 함수는 attrs 란 이름의 dictionary를 받습니다.

    from rest_framework import serializers
    
    class EventSerializer(serializers.Serializer):
        description = serializers.CharField(max_length=100)
        start = serializers.DateTimeField()
        finish = serializers.DateTimeField()
    
        def validate(self, attrs):
            """
            Check that the start is before the stop.
            """
            if attrs['start'] > attrs['finish']:
                raise serializers.ValidationError("finish must occur after start")
            return attrs

    Dealing with nested objects

    복잡한 객체에 대한 표현이 필요로 할 때, 단순한 string, int로는 표현이 안될 수 있습니다. 

    Serializer클래스는 그 자체로 필드 타입이고 이 Serializer는 또 다른 객체타입과의 관계를 표현할 수 있습니다. (중복적 표현)

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


    기본적으로 중복 표현은 None 값을 받고자 하는 경우 required=False를 추가해야 합니다.

    class CommentSerializer(serializers.Serializer):
        user = UserSerializer(required=False)  # May be an anonymous user.
        content = serializers.CharField(max_length=200)
        created = serializers.DateTimeField()


    중복 표현 값에 대해 list로 받고자 하는 경우, many=True를 추가합니다.

    class CommentSerializer(serializers.Serializer):
        user = UserSerializer(required=False)
        edits = EditItemSerializer(many=True)  # A nested list of 'edit' items.
        content = serializers.CharField(max_length=200)
        created = serializers.DateTimeField()


    중첩 된 개체의 유효성 검사는 이전과 동일하게 작동합니다. 중첩 된 개체 오류가 중첩 된 객체의 필드 이름 아래에 중첩됩니다.

    serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
    serializer.is_valid()
    # False
    serializer.errors
    # {'user': {'email': [u'Enter a valid e-mail address.']}, 'created': [u'This field is required.']}

    Dealing with multiple objects

    Serializing multiple objects

    다수의 데이터 queryset 형태를 serialize화 하고자 할 때, many=True를 사용합니다.

    queryset = Book.objects.all()
    serializer = BookSerializer(queryset, many=True)
    serializer.data
    # [
    #     {'id': 0, 'title': 'The electric kool-aid acid test', 'author': 'Tom Wolfe'},
    #     {'id': 1, 'title': 'If this is a man', 'author': 'Primo Levi'},
    #     {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'}
    # ]

    Deserializing multiple objects for creation

    다수의 데이터를 리스트 형식으로 삽입하고자 할 때 다음과 같이 사용합니다. 

    data = [
        {'title': 'The bell jar', 'author': 'Sylvia Plath'},
        {'title': 'For whom the bell tolls', 'author': 'Ernest Hemingway'}
    ]
    serializer = BookSerializer(data=data, many=True)
    serializer.is_valid()
    # True
    serializer.save()  # `.save()` will be called on each deserialized instance

    Deserializing multiple objects for update

    다수의 데이터를 리스트 형식으로 업데이트 하고자 할 때 다음과 같이 사용합니다. 이때, 기존 데이터의 list와 변경하고자 하는 list를 같이 전송해야 합니다.

    # Capitalizing the titles of the books
    queryset = Book.objects.all()
    data = [
        {'id': 3, 'title': 'The Bell Jar', 'author': 'Sylvia Plath'},
        {'id': 4, 'title': 'For Whom the Bell Tolls', 'author': 'Ernest Hemingway'}
    ]
    serializer = BookSerializer(queryset, data=data, many=True)
    serializer.is_valid()
    # True
    serializer.save()  # `.save()` will be called on each updated or newly created instance


    기본적으로 대량 업데이트를 이미 제공하고 있는 queryset 타입은 위와 같은 방법이 제한됩니다. 또한 대량의 업데이트를 수행할 때, 새로 작성한 필드의 값에 대해서만 업데이트를 하고자 원하는 경우, 해당 필드의 값을 나열하지 않는 경우 필드의 값이 삭제가 됩니다. 이와 같은 효과를 내기 위해선, allow_add_remove=True를 추가합니다.

    serializer = BookSerializer(queryset, data=data, many=True, allow_add_remove=True)
    serializer.is_valid()
    # True
    serializer.save()  # `.save()` will be called on updated or newly created instances.
                       # `.delete()` will be called on any other items in the `queryset`.

    allow_add_remove=True를 사용할 시, 모든 업데이트 작업이 완전히 단순히 기존에 존재하는 객체에 대해서 업데이트하는 것이 아니라, 사용자가 정의한 데이터로 queryset을 덮어 쓰게 됩니다.

    How identity is determined when performing bulk updates

    대량의 데이터에 대해 업데이트를 수행하는 것은 대량 데이터 생성보다 복잡합니다. serializer가 이미 존재하는 객체 인스턴스와 사용자가 업데이트 하고자하는 데이터 간 매칭을 어떻게 결정할 지에 대한 방법이 필요하기 때문입니다.

    기본적으로 serializer 클래스는 객체의 pk를 결정하기 위해 받은 데이터의 id(default) key값을 사용합니다. 만약 이러한 행동을 변경해야 되는 경우(pk값이 id가 아닌 다른 컬럼 명), Serializer 클래스에 get_identity()함수를 override해야 합니다.

    class AccountSerializer(serializers.Serializer):
        slug = serializers.CharField(max_length=100)
        created = serializers.DateTimeField()
        ...  # Various other fields
    
        def get_identity(self, data):
            """
            This hook is required for bulk update.
            We need to override the default, to use the slug as the identity.
    
            Note that the data has not yet been validated at this point,
            so we need to deal gracefully with incorrect datatypes.
            """
            try:
                return data.get('slug', None)
            except AttributeError:
                return None

    To map the incoming data items to their corresponding object instances, the .get_identity() method will be called both against the incoming data, and against the serialized representation of the existing objects.

    (표현이 안됨;)

    ModelSerializer

    먼저 모델을 받아옵니다.

    from django.db import models 
    # models.Model 상속받아 정의합니다.
    class User(models.Model):
        user_seq = models.AutoField(primary_key=True, help_text='PK AutoIncrement')
        user_id = models.CharField(max_length=20, blank=False, null=False, unique=True, help_text='사용자 아이디')
        user_password = models.CharField(max_length=250, blank=False, null=False, help_text='사용자 비밀번호')
        user_name = models.CharField(max_length=30, blank=False, null=False, help_text='사용자 이름')
        birthday = models.DateField(auto_now=False, blank=False, null=False, help_text='사용자 생년월일')
        email = models.EmailField(max_length=100, blank=False, null=False, help_text='사용자 이메일')
        mobile = models.CharField(max_length=15, blank=False, null=False, help_text='사용자 휴대폰 전화번호')
        age = models.IntegerField(blank=False, null=False, help_text='사용자 나이')
        insert_datetime = models.DateTimeField(auto_now=True, blank=True, null=True, help_text='등록일')
     
        class Meta:
            managed = False
            db_table = 't_user'


    다음은 serializer의 기본 모습입니다. ModelSerializer 클래스를 사용하면 Model에 정의한 필드에 해당하는 값을 serializer에서 사용할 수 있습니다.

    from rest_framework import serializers
    from test.models import User
     
    Class UserSerializer(serializers.ModelSerializer):
    	class Meta:
    		model = User
    		# fields = ('__all__',) fields가 없으면 default로 테이블 내 모든 컬럼을 사용할 수 있음

    model에서 foreign key와 같은 relationship의 경우는 PrimaryKeyRelatedField와 매핑이 됩니다. 다른 model의 필드들은 serializer에서 사용하는 필드와 맵핑이 됩니다.

    만약 ModelSerializer의 validate 검증이 일어날 때에 serializer의 field와 대응되는 model의 field 둘다 정확하게 검증되어야 합닏. 만약 모델에 추가적인 fields를 가진 경우,  model의 field에 blank=True와 serializer 필드에 required=False를 설정해야 합니다.

    특정 fields 사용

    model의 컬럼들 중, 특정 컬럼만 사용하고 싶은 경우 다음과 같이 사용합니다.

    class AccountSerializer(serializers.ModelSerializer):
        class Meta:
            model = Account
            fields = ('id', 'account_name', 'users', 'created')

    read-only fields

    read only 필드로 다수 필드를 구체화 할 수 있습니다. read_only=True 속성으로 추가하고자 하는 필드를 명시해야 하는 것 대신, read_only_fields 라는 meta 옵션을 사용할 수 있습니다.

    class AccountSerializer(serializers.ModelSerializer):
        class Meta:
            model = Account
            fields = ('id', 'account_name', 'users', 'created')
            read_only_fields = ('account_name',)

    write-only fields

    write only 필드로 다수 필드를 구체화 할 수 있습니다. write_only=True 속성으로 추가하고자 하는 필드를 명시해야 하는 것 대신, write_only_fields 라는 meta 옵션을 사용할 수 있습니다.

    class CreateUserSerializer(serializers.ModelSerializer):
        class Meta:
            model = User
            fields = ('email', 'username', 'password')
            write_only_fields = ('password',)  # Note: Password field is write-only
    
        def restore_object(self, attrs, instance=None):
            """
            Instantiate a new User instance.
            """
            assert instance is None, 'Cannot update users with CreateUserSerializer'
            user = User(email=attrs['email'], username=attrs['username'])
            user.set_password(attrs['password'])
            return user

    Specifying fields explicitly

    추가적인 fields를 ModelSerializer에 추가 또는 override할 수 있습니다.

    class AccountSerializer(serializers.ModelSerializer):
        url = serializers.CharField(source='get_absolute_url', read_only=True)
        groups = serializers.PrimaryKeyRelatedField(many=True)
    
        class Meta:
            model = Account

    HyperlinkedModelSerializer

    HyperlinkedModelSerializer 클래스는 오히려 pk보다 relationship을 표현하기 위해 하이퍼 링크를 사용하는 것을 제외하고는 ModelSerializer 클래스와 유사합니다. 기본적으로 serializer는 pk필드 대신에 url 필드를 포함합니다. URL 필드는 HyperlinkedIdentityField 시리얼 필드를 이용하여 표현되며, 모델의 관계는 HyperlinkedRelatedField 시리얼 필드를 사용하여 표현됩니다.

    아래의 예처럼, fields 옵션을 추가함으로써 pk를 명시적으로 포함할 수 있습니다.

    class AccountSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = Account
            fields = ('url', 'id', 'account_name', 'users', 'created')

    Hyperlinked된 view를 어떻게 결정할 것인가

    인스턴스를 모델링하기 위해 하이퍼 링크에 사용되어야하는 뷰를 판정하는 방법이 필요합니다. 

    기본적으로 하이퍼 링크는 '{model_name}-detail'와 일치하는 view 이름에 해당할 것으로 예상되며, pk 키워드 argument로 인스턴스를 찾습니다.

    lookup_field 옵션을 설정하여 객체 조회에 사용되는 필드를 변경할 수 있습니다. 이 옵션의 가치는 URL의 kwarg 설정과 모델에 필드 모두 대응합니다.

    class AccountSerializer(serializers.HyperlinkedModelSerializer):
        url = serializers.HyperlinkedIdentityField(
            view_name='account_detail',
            lookup_field='account_name'
        )
        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')

    URL 필드 동작 재정의

    URL필드의 기본 이름은 URL입니다. URL_FIELD_NAME 필드를 사용하여 이것을 전세계적으로 재정의할 수 있습니다. 또한 serializer 내의 url_field_name옵션을 사용하여 serializer 별로 url을 설정할 수 있습니다.

    class AccountSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = Account
            fields = ('account_url', 'account_name', 'users', 'created')
            url_field_name = 'account_url'

    일반적인 view 구현은 성공적인POST 요청에 응답 Location 헤더를 생성합니다. url_field_name 옵션을 사용한 serializer는 view에 의해 자동적으로 추가되는 이 헤더가 존재하지 않습니다. 만약 이와 같은 행위가 필요할 경우, view의 get_success_headers() 함수를 오버라이드하는 것 입니다.

    또한 view_name과 lookup_field 옵션을 사용해 명시적으로 field에 override 없이 URL 필드의 view 이름과 조회 field를 재정의할 수 있습니다.

    class AccountSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
            model = Account
            fields = ('account_url', 'account_name', 'users', 'created')
            view_name = 'account_detail'
            lookup_field='account_name'


    댓글