현재 Custom Authentication을 구현하여 request.user
에 Header에 들어있는 특정한 값을 파싱하여 넣도록 구현했다. 그 다음으로 리퀘스트를 검증하기위해 Serializer를 사용하는데, 일반적으로 시리얼라이저는 정의해놓은 필드이외의 값을 받지 못한다. 예를 들어 아래와 같은 시리얼라이저가 있다고 가정해보자.
class CreatePostRequestSerializer(serializers.Serializer):
body = serializers.CharField(required=False)
attachments = serializers.ListField(required=False)
리퀘스트로는 body
, attachments
두개의 값이 들어오고 제대로 값이 들어왔는지 판단하는 시리얼라이저다. 이 부분에서 is_valid()
이후 serializer.data
를 찍으면 request.user에 들어있는 유저의 아이디값을 같이 찍어주고 싶었다. 다음과 같은 검증되지 않은 플로우를 생각했다.
- 시리얼라이저에 리퀘스트를 대입할 때 request.data등의 값이 아닌 request객체 자체를 넘긴다.
- 검증할 때 클래스 내부 변수를 생성한다음 request.user의 값으로 채워주고 검증 대상은 request.data로 교체한다.
- 최종적으로 .data를 통해 검증된 값을 출력할 때 2번에서 생성한 클래스 내부 변수를 포함하여 리턴한다.
좀 찾아보니 CreatePostRequestSerializer(data=리퀘스트)
와 같은 형식으로 시리얼라이저에 값을 삽입하면 to_internal_value()
라는 함수가 먼저 호출된다. 따라서 시리얼라이저 내부에 해당 함수를 오버라이딩한다음 원하는 내용을 넣어주면 된다.
def to_internal_value(self, data):
from collections.abc import Mapping
from rest_framework.exceptions import ValidationError
from rest_framework.settings import api_settings
from collections import OrderedDict
from django.core.exceptions import \
ValidationError as DjangoValidationError
from rest_framework.fields import get_error_detail, set_value
from rest_framework.fields import SkipField
from rest_framework.request import Request
"""
Dict of native values <- Dict of primitive datatypes.
"""
if isinstance(data, Request):
setattr(self, 'user_id', int(data.user))
data = data.data
if not isinstance(data, Mapping):
message = self.error_messages['invalid'].format(
datatype=type(data).__name__
)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
}, code='invalid')
ret = OrderedDict()
errors = OrderedDict()
fields = self._writable_fields
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name,
None)
primitive_value = field.get_value(data)
try:
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
errors[field.field_name] = get_error_detail(exc)
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
모든 코드는 기존 함수의 코드와 동일하고 if isinstance(data, Request)
를 포함한 아래 2줄만 추가한 부분이다. 범용적으로 사용하기위해 입력값이 Request
객체 자체일때는 user_id
라는 변수에 request.user
의 값을 넣어주고(해당 함수에서는 request가 data라는 네이밍으로 넘어오므로 data.user) 실제로 Validation을 진행하는 변수는 data.data(마찬가지로 실제로는 request.data)값을 대입해줬다.
마지막으로 data()
함수로 오버라이딩을 해줘야한다.
@property
def data(self):
from rest_framework.utils.serializer_helpers import ReturnDict
ret = super().data
ret['user_id'] = self.user_id
return ReturnDict(ret, serializer=self)
ret에 최종적으로 리턴될 데이터가 들어있으므로 위에서 생성하고 값을 넣어줬던 user_id
변수를 ret 딕셔너리에 포함시킨 후 리턴해준다.
조금 어거지로 변형한 것 같긴한데, 나름 목적을 이뤘으니 실제 프로덕트에 적용할지는 앞으로 생각하면 될 것 같다.
'Coding > Python' 카테고리의 다른 글
Django Middleware로 특정 주소 Redirect 시키는 방법 (0) | 2019.10.21 |
---|---|
Python Alembic으로 Migration 관리하는 방법 (0) | 2019.09.26 |
Python dataclass property/setter (0) | 2019.09.18 |
Python Requests Retry (0) | 2019.09.05 |
Python Method Chaining (0) | 2019.07.17 |