1. Serializers
1) Serializer란?
- Queryset과 Model 인스턴스와 같은 복잡한 타입을 JSON, XML 등으로 쉽게 렌더링될 수 있는 고유의 파이썬 데이터 타입으로 변환해준다. 이를 직렬화라고 한다.
- 파싱된 데이터를 Queryset과 Model 인스턴스와 같은 복잡한 타입으로 변환해주는 역직렬화도 지원한다.
- DRF에서 제공하는 Serializer 클래스는 응답의 출력을 제어할 수 있는 강력하고 일반적인 방법을 제공한다. 또, Queryset과 Model 인스턴스들을 다루는 serializer인 ModelSerializer도 추가로 제공한다.
2) Serializer 선언하기
- Serializer 클래스를 상속받는 식으로 간단하게 만들 수 있다. 폼이나 모델과 유사한 모습이다.
from rest_framework import serializers
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
3) 직렬화하기
- 오브젝트를 시리얼라이저의 instance 파라미터로 넣어 시리얼라이저를 생성한다. instance는 기본 파라미터기 때문에 키워드는 생략 가능하다.
- 오브젝트를 렌더링한 결과는 Serializer의 data 속성에 있다.
serializer = CommentSerializer(comment) # CommentSerializer(instance=comment)와 동일
serializer.data # {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}
그래서 최종적으로 다음과 같이 클라이언트에게 응답을 줄 수 있다.
return Response(serializer.data)
4) 역직렬화하기
- 직렬화된 데이터를 시리얼라이저의 data 파라미터로 넣어 시리얼라이저를 생성한다.
serializer = CommentSerializer(data=data)
serializer.is_valid() # True
serializer.validated_data # {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}
- is_valid()는 필드들의 유효성 검사를 수행해주는 메소드로 필수적으로 필요하다. 유효성 검사에 실패하면 오류 내용이 시리얼라이저의 errors 속성에 담기고, 유효성 검사에 통과한 데이터들은 validated_data 속성에 담기게 된다.
5) 인스턴스 저장하기
- 시리얼라이저를 통해 인스턴스를 생성하고 싶다면 create() 메소드를 구현하고, 인스턴스의 수정이 필요하다면 update() 메소드를 구현한다. create()와 update() 메소드는 둘 다 선택적으로 구현하면 된다.
- 유효성 검사(is_valid)에 통과한 데이터들이 담긴 validated_data로 인스턴스의 속성을 채워넣는 방식이다.
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
이후에 시리얼라이저의 save() 메소드를 호출해서 인스턴스를 저장하고 반환받을 수 있으며,
comment = serializer.save()
기존 인스턴스가 시리얼라이저를 통과했는지 여부에 따라 create(), update() 호출이 결정된다.
# .save() will create a new instance.
serializer = CommentSerializer(data=data)
# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)
- 인스턴스를 저장하는 시점에 추가적인 데이터를 주입하고 싶다면 save() 메소드에 넣을 수도 있다. 추가된 키워드 인자는 create() 또는 update() 메소드가 호출될 때 validated_data에 포함된다.
serializer.save(owner=request.user)
- 인스턴스를 생성하거나 수정할 필요가 없는 경우에는 save() 메소드를 오버라이딩할 수 있다. 이 경우에는 self를 통해 validated_data에 직접 접근할 수 있다.
class ContactSerializer(serializers.Serializer):
email = serializers.EmailField()
message = serializers.CharField()
def save(self):
email = self.validated_data['email']
message = self.validated_data['message']
send_email(from=email, message=message)
6) 검증
(1) 검증 결과
- 유효성 검사에 통과한 데이터들은 시리얼라이저의 validated_data에 저장되며, 인스턴스를 저장할 때는 validated_data에 담긴 데이터가 사용된다고 언급했다. 그래서 역직렬화시에는 반드시 is_valid() 메소드가 호출되어야 한다.
- 유효성 검사에 실패했을 때는 오류 내용이 시리얼라이저의 errors 속성에 담긴다는 사실 또한 언급했다.
serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']}
errors 딕셔너리의 각 키는 일반적으로 필드의 이름이고, 딕셔너리의 값은 해당 필드에 대한 에러 메시지 리스트다. 특정 필드 하나만의 에러가 아닌 경우에는 기본적으로 "non-field-errors"이 키 값이 되는데, 이 값을 커스터마이징 하고 싶다면 settings.py에서 NON_FIELD_ERRORS_KEY 항목을 지정하면 된다.
REST_FRAMEWORK = {
'NON_FIELD_ERRORS_KEY': 'your_key_here',
}
(2) Field-level 검증
- validate_<field_name>() 메소드를 추가함으로써 검증을 요하는 특정 필드에 대한 검증 로직을 추가할 수 있다. 이 메소드는 검증된 값을 반환하거나 ValidationError를 일으키도록 해야 한다.
from rest_framework import serializers
class BlogPostSerializer(serializers.Serializer):
title = serializers.CharField(max_length=100)
content = serializers.CharField()
def validate_title(self, value):
"""
Check that the blog post is about Django.
"""
if 'django' not in value.lower():
raise serializers.ValidationError("Blog post is not about Django")
return value
- errors의 키 값은 자동으로 필드의 이름으로 세팅된다.
(3) Object-level 검증
- 특정 필드 하나에 대한 검증이 아닌 경우에는, 검증 로직을 validate() 메소드에 작성하면 된다.
from rest_framework import serializers
class EventSerializer(serializers.Serializer):
description = serializers.CharField(max_length=100)
start = serializers.DateTimeField()
finish = serializers.DateTimeField()
def validate(self, data):
"""
Check that start is before finish.
"""
if data['start'] > data['finish']:
raise serializers.ValidationError("finish must occur after start")
return data
- errors의 키 값은 기본적으로 자동으로 "non-field-errors"으로 세팅된다.
(4) Validators
- 시리얼라이저의 각 필드에 validator들을 넣는 방법도 있다.
def multiple_of_ten(value):
if value % 10 != 0:
raise serializers.ValidationError('Not a multiple of ten')
class GameRecord(serializers.Serializer):
score = IntegerField(validators=[multiple_of_ten])
...
- 내부 Meta 클래스에 validator들을 넣는 방법도 있다.
class EventSerializer(serializers.Serializer):
name = serializers.CharField()
room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
date = serializers.DateField()
class Meta:
# Each room only has one event per day.
validators = [
UniqueTogetherValidator(
queryset=Event.objects.all(),
fields=['room_number', 'date']
)
]
DRF에서 제공하는 더 많은 Validator 클래스들을 알고 싶다면 아래 공식문서에서 확인하면 된다. 조만간 한글화를 해서 블로그에 올리도록 하겠다.
https://www.django-rest-framework.org/api-guide/validators/
7) 인스턴스의 초기 데이터에 접근하기
- 시리얼라이저를 생성할 때 파라미터로 instance를 지정했다면 시리얼라이저의 instance 속성을 통해 인스턴스에 접근할 수 있다. create() 메소드에서는 self.instance 값이 None이라는 사실 또한 알 수 있을 것이다.
- 시리얼라이저를 생성할 때 파라미터로 data를 지정했다면 시리얼라이저의 initial_data 속성을 통해 초기 데이터에 접근할 수 있다.
8) 일부 필드 수정하기
- 기본적으로 시리얼라이저는 필수 필드들을 넘겨받지 못하면 에러를 발생시킨다. partial=True로 지정함으로써 일부 필드를 수정하도록 허용할 수 있다.
# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True)
9) 관계형 필드 다루기
- 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()
- 자식 시리얼라이저에 required=False 옵션을 줌으로써 None도 허용 가능하다.
user = UserSerializer(required=False) # May be an anonymous user.
- 자식 시리얼라이저가 여러 오브젝트를 나타내는 경우에는 many=True 옵션을 주면 된다.
edits = EditItemSerializer(many=True) # A nested list of 'edit' items.
10) 쓰기 가능한 중첩 표현
- 중첩 시리얼라이저는 중첩된 데이터 구조로 주고 받는다. validated_data 속성 역시 중첩된 데이터 구조다.
serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'user': {'email': ['Enter a valid e-mail address.']}, 'created': ['This field is required.']}
- create(), update()에서는 다음과 같이 pop()을 활용하여 객체를 따로 생성하거나 수정하는 방식을 사용한다.
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ['username', 'email', 'profile']
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user
def update(self, instance, validated_data):
profile_data = validated_data.pop('profile')
# Unless the application properly enforces that this field is
# always set, the following could raise a `DoesNotExist`, which
# would need to be handled.
profile = instance.profile
instance.username = validated_data.get('username', instance.username)
instance.email = validated_data.get('email', instance.email)
instance.save()
profile.is_premium_member = profile_data.get(
'is_premium_member',
profile.is_premium_member
)
profile.has_support_contract = profile_data.get(
'has_support_contract',
profile.has_support_contract
)
profile.save()
return instance
- 부모, 자식 오브젝트가 동시에 생성된다는 것을 효과적으로 캡슐화하기 위해 관리자 클래스에서 create() 메소드를 작성할 수도 있다.
class UserManager(models.Manager):
...
def create(self, username, email, is_premium_member=False, has_support_contract=False):
user = User(username=username, email=email)
user.save()
profile = Profile(
user=user,
is_premium_member=is_premium_member,
has_support_contract=has_support_contract
)
profile.save()
return user
이제 시리얼라이저의 create() 메소드에서는 관리 클래스에서 작성한 create()에 맞게 작성해주면 된다.
def create(self, validated_data):
return User.objects.create(
username=validated_data['username'],
email=validated_data['email'],
is_premium_member=validated_data['profile']['is_premium_member'],
has_support_contract=validated_data['profile']['has_support_contract']
)
11) 다중 오브젝트 다루기
- Serializer 클래스는 오브젝트 리스트를 직렬화, 역직렬화할 수 있다.
(1) 다중 오브젝트 직렬화
- queryset이나 오브젝트 리스트를 다루기 위해 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'}
# ]
(2) 다중 오브젝트 역직렬화
- 기본적으로 다중 오브젝트를 역직렬화할 때, 다중 오브젝트 생성은 지원하지만 다중 오브젝트 수정은 지원하지 않는다. 이러한 동작을 커스터마이징 하고 싶다면 잠시 후 살펴볼 ListSerializer을 참고하자.
12) 추가 context 포함하기
- 직렬화 외에도 추가적인 컨텍스트를 제공하고 싶다면 다음과 같이 context 옵션에 추가해주면 된다.
serializer = AccountSerializer(account, context={'request': request})
serializer.data
# {'id': 6, 'owner': 'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}
- 컨텍스트 딕셔너리는 to_representation() 메소드 등 모든 직렬화 필드 로직 내에서 self.context로 접근 가능하다.
2. ModelSerializer
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['id', 'account_name', 'users', 'created']
- 모델 정의와 밀접하게 연결되는 Serializer 클래스로, 모델을 기반으로 필드 집합과 validator들을 자동으로 생성해준다.
- create(), update()의 단순한 기본 구현이 포함되어 있다.
- 외래 키와 같은 모든 관계형 필드는 PrimaryKeyRelatedField에 매핑된다. 역방향 관계는 기본적으로 포함되지 않는다.
1) ModelSerializer 검사하기
- 필드의 상태를 검사하기 위해 다음과 같이 상세 표현 문자열을 확인해볼 수 있다.
>>> from myapp.serializers import AccountSerializer
>>> serializer = AccountSerializer()
>>> print(repr(serializer))
AccountSerializer():
id = IntegerField(label='ID', read_only=True)
name = CharField(allow_blank=True, max_length=100, required=False)
owner = PrimaryKeyRelatedField(queryset=User.objects.all())
2) 포함할 필드 명시하기
- 모델 시리얼라이저에서 사용할 필드들을 명시하기 위해 fields 또는 exclude 옵션을 사용해야 한다. fields를 통해 직렬화해야 하는 모든 필드를 명시적으로 설정하는 방식이, 모델이 변경될 때 의도치 않게 데이터가 노출될 가능성이 줄어든다.
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['id', 'account_name', 'users', 'created']
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
exclude = ['users']
- '__all__'라는 특별한 값을 통해 모델의 모든 필드를 사용하도록 할 수 있다.
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = '__all__'
- fields 옵션안에 있는 이름은 인자를 필요로 하지 않는 속성이나 메소드와 매핑될 수 있다.
3) 중첩 직렬화 명시하기
- depth 옵션을 통해 중첩 표현을 쉽게 생성할 수 있다. depth 옵션은 직렬화할 때 관련 모델의 관계를 몇 단계 깊이까지 직렬화할지를 지정한다.
class BookSerializer(serializers.ModelSerializer):
author = AuthorSerializer(read_only=True)
class Meta:
model = Book
fields = ['id', 'title', 'author']
depth = 1
만약에 지정된 depth까지 도달하지 않는 중첩 시리얼라이저의 경우에는 기본키(단일값)로 직렬화한다.
4) 추가 필드 명시하기
- 추가 필드를 명시하거나 기본 필드들을 오버라이딩할 수 있다.
class AccountSerializer(serializers.ModelSerializer):
url = serializers.CharField(source='get_absolute_url', read_only=True)
groups = serializers.PrimaryKeyRelatedField(many=True)
class Meta:
model = Account
fields = ['url', 'groups']
source 옵션은 시리얼라이저 필드가 데이터를 어디서 가져와야 하는지를 지정하는 옵션으로, 위의 예시에서는 모델에 있는 get_absolute_url() 메소드의 반값을 사용한다.
- 추가 필드는 모델의 속성이나 호출 가능한 모든 항목과 연결될 수 있다.
5) 읽기 전용 필드들 명시하기
- 동시에 여러개의 read-only 필드를 지정하고 싶다면 Meta 클래스의 read_only_fields를 이용하면 된다. 리스트 또는 튜플이 올 수 있다.
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['id', 'account_name', 'users', 'created']
read_only_fields = ['account_name']
※ editable=False로 지정된 필드 및 AutoField는 기본적으로 read-only로 세팅된다.
- read-only 필드가 unique_together 제약조건의 일부인 경우가 있다. 이러한 특수한 경우에는 제약 조건의 검사를 위해 이 필드는 필수가 되며, 사용자에 의해 수정될 수도 없다. 이 경우를 다루기 위한 올바른 방법은 시리얼라이저에 해당 필드를 read_only=True, default=... 두 옵션을 포함하여 명시하는 것이다.
user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
6) 추가 키워드 인자
- Meta 클래스의 extra_kwargs 딕셔너리 옵션을 통해 필드에 추가적인 키워드 인자들을 지정할 수 있다. 필드에 추가적인 옵션을 주기 위해 시리얼라이저에 추가적인 필드를 선언할 필요가 없게 된다.
class CreateUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['email', 'username', 'password']
extra_kwargs = {'password': {'write_only': True}}
def create(self, validated_data):
user = User(
email=validated_data['email'],
username=validated_data['username']
)
user.set_password(validated_data['password'])
user.save()
return user
- 시리얼라이저 필드가 이미 명시적으로 선언되어 있는 경우에는 extra_kwargs 옵션은 무시된다.
7) 관계형 필드
- 모델 인스턴스를 직렬화할 때 관계 표현을 위한 여러가지 방법이 있다. 기본적으로는 관계 인스턴스의 기본키를 사용한다. 대체 표현으로는 하이퍼링크, 완전한 중첩 표현, 사용자 정의 표현이 있다.
8) 필드 매핑 커스터마이징
- 시리얼라이저를 인스턴스화할 때 시리얼라이저 필드가 자동으로 결정되는 방식을 변경하기 위해 재정의 할 수 있는 API를 제공한다.
- 일반적으로 ModelSerializer가 원하는 필드를 기본적으로 생성하지 않는 경우에는 클래스에 필드를 명시적으로 추가하거나 정규 Serializer 클래스를 사용해야 하지만, 모델에 대해 시리얼라이저 필드가 생성되는 방법을 정의하는 새 베이스 클래스를 만들 수 있다.
(1) 필드 클래스 커스터마이징
● serializer_field_mapping
- 모델 필드를 시리얼라이저 필드에 매핑한다. 이 매핑을 재정의하여 각 모델 필드에 사용할 기본 시리얼라이저 필드를 변경할 수 있다.
class MyModelSerializer(serializers.ModelSerializer):
serializer_field_mapping = {
models.IntegerField: serializers.IntegerField,
models.CharField: serializers.CharField,
models.BooleanField: serializers.BooleanField,
# ...
}
● serializer_related_field
- 이 속성은 기본적으로 관계형 필드에 사용될 시리얼라이저 필드 클래스여야 한다.
serializer_related_field = PrimaryKeyRelatedField
- ModelSerializer의 경우 기본값으로 serializers.PrimaryKeyRelatedField를 사용하고, HyperlinkedModelSerializer의 경우 기본값으로 serializers.HyperlinkedRelatedField를 사용한다.
● serializer_url_field
- 시리얼라이저의 모든 url 필드에 사용될 시리얼라이저 필드 클래스로, 기본값으로 serializers.HyperlinkedIdentityField를 사용한다.
● serializer_choice_field
- 시리얼라이저의 모든 선택 필드에 사용될 시리얼라이저 필드 클래스로, 기본값으로 serializers.ChoiceField를 사용한다.
(2) 필드 클래스 및 키워드 인자 커스터마이징
다음 메소드를 통해 시리얼라이저에 자동으로 포함되어야 하는 필드 클래스 및 키워드 인자를 결정한다. 각 메소드는 두 개의 튜플 (field_class, field_kwargs)을 반환해야 한다.
● build_standard_field(self, field_name, model_field)
- 표준 모델 필드에 매핑되는 시리얼라이저 필드를 생성하기 위해 호출된다. 기본적으로 serializer_field_mapping 속성을 기반으로한 시리얼라이저 클래스를 반환하도록 구현되어있다.
def build_standard_field(self, field_name, model_field):
# Make all fields optional
model_field.blank = True
return super().build_standard_field(field_name, model_field)
● build_relational_field(self, field_name, relation_info)
- 관계형 필드에 매핑되는 시리얼라이저 필드를 생성하기 위해 호출된다. 기본적으로 serializer_related_field 속성을 기반으로한 시리얼라이저 클래스를 반환하도록 구현되어있다.
- relation_info 인자는 model_field, related_model, to_many, has_through_model 속성을 포함하는 명명된 튜플이다.
● build_nested_field(self, field_name, relation_info, nested_depth)
- depth 옵션이 설정된 경우에 관계형 필드에 매핑되는 시리얼라이저 필드를 생성하기 위해 호출된다. 기본적으로 ModelSerializer 또는 HyperlinkedModelSerializer을 기반으로한 중첩 시리얼라이저 클래스를 동적으로 생성하도록 구현되어있다.
- relation_info 인자는 model_field, related_model, to_many, has_through_model 속성을 포함하는 명명된 튜플이다.
● build_property_field(self, field_name, model_class)
- 모델 클래스의 속성이나 인자 없는 메소드에 매핑되는 시리얼라이저 필드를 생성하기 위해 호출된다. 기본적으로 ReadOnlyField 클래스를 반환하도록 구현되어있다.
● build_url_field(self, field_name, model_class)
- 시리얼라이저 자체의 url 필드에 대한 시리얼라이저 필드를 생성하기 위해 호출된다. 기본적으로 HyperlinkedIdentityField 클래스를 반환하도록 구현되어있다.
● build_unknown_field(self, field_name, model_class)
- 필드 이름이 모델 필드나 모델 속성에 매핑되지 않은 경우 호출된다. 하위 클래스가 이 동작을 커스터마이징할 수 있지만 기본 구현에서는 오류가 발생한다.
3. HyperlinkedModelSerializer
(작성중)
'Django Rest Framework' 카테고리의 다른 글
[DRF] BasePermission으로 커스텀 권한 만들기 (0) | 2023.03.09 |
---|---|
[DRF] DRF 뷰의 발전 과정 (0) | 2023.02.23 |
[DRF] DRF Serializer (0) | 2023.02.21 |
[DRF] Django REST Framework란 무엇일까? (0) | 2023.02.21 |
댓글