super()의 create() 메서드에 임의의 POST 데이터 넣기

개발을 파헤치다/Django|2018. 5. 10. 10:00

Django REST Framework는 개발자의 생산성 향상을 위해 내부적으로 다양한 처리들을 제공합니다.

POST 필드값들의 유효성 검사부터 Serializer를 통해 Model Instance의 생성 및 삭제까지 모두 기본적으로 제공하고 있습니다.

이것만 봐도 Django REST Framework가 추구하는 방향성을 알 수 있습니다.

바로 뛰어난 생산성이죠.

많은 프로젝트에서 반복적으로 구현해야 하는 기본적인 것들을 모두 제공하고, 필요에 따라 개발자들이 Customizing을 하여 사용할 수 있도록 한 것입니다.

그렇기 때문에 Django REST Framework(이하 DRF)의 장점을 극대화하여 사용하려면 최대한 기본적으로 제공해주는 메서드를 활용하는 것이 좋습니다.

이번에는 REST API의 POST에 대응되는 ViewSet의 create 메서드를 사용할 때 응용할 수 있는 작은 팁을 제시하고자 합니다.

아래의 예시를 보면서 문제상황과 어떻게 해결할 수 있는지 살펴봅니다.

#viewsets.py   
def create(self, request, *args, **kwargs):
self.serializer_class = BusinessUser_Create_Serializer
return super().create(request, *args, **kwargs)

위는 회원가입을 처리하는 viewset의 예시입니다.

회원가입에 알맞은 serializer를 배치하고 부모 클래스의 create 메서드를 호출하면 request에 포함된 POST 데이터의 유효값을 검증합니다.

만약 검증하다 에러가 발생하면 사용자에게 알아서 Error를 리턴해줍니다.

에러를 통과하면 Serializer를 통해 Model Instance를 생성합니다.

이렇게 내부적으로 많은 기능들을 DRF에서는 제공해주고 있습니다.

하지만 이때 발생할 수 있는 문제점도 존재합니다.

바로 request에 포함된 POST 데이터를 변경할 수 없다는 것입니다.

예를 들면 다음과 같은 상황이 발생할 수 있습니다.

class BusinessUser_Manage_Serializer(serializers.ModelSerializer):
...
hashtag = Accounts_Hashtag_Manage_Serializer(required=False, many=True)
...

위처럼 Serializer에서는 hashtag가 POST 값으로 들어오면 검증을 하게 됩니다.

이때 hashtag를 List의 형태로 인식하게 되는데 문제는 여기에서 발생할 수 있습니다.

왜냐하면 hashtag를 JSON화된 문자열로 보내기 때문입니다.

이 경우 해결할 수 있는 방법 중 하나는 Request에 포함된 hashtag를 List형태로 바꾸어 부모 클래스의 create()메서드에 보내주는 것입니다.

하지만 이것을 하기 위해서는 먼저 create()메서드가 어떻게 처리되는지 살펴볼 필요가 있습니다.

#viesets.py
def create(self, request, *args, **kwargs)
super().create(request, *args, **kwargs)

위의 메서드를 호출하면 다음의 Create Model Mixin을 거치게됩니다.

class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

def perform_create(self, serializer):
serializer.save()

def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}

위의 코드를 보면 request의 data attribute를 가져와서 Serializer를 통해 유효성을 검사한 뒤 Serializer의 create를 호출하는 것을 볼 수 있습니다.

reqeust.data에는 HTTP Multi Part Form으로 전송된 POST 필드값과 File들이 들어가있습니다.

따라서 이것을 토대로 Create Model Mixin을 변경하면 기본적으로 제공하는 여러 절차들을 수행하면서도 커스텀된 POST 데이터값을 넣을 수 있습니다.

핵심은 request의 data필드만 가져와서 사용하기 때문에 이 부분에 커스텀된 data를 넣을 수 있도록 하면 된다는 것입니다.

class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):

if isinstance(request, Request):
serializer = self.get_serializer(data=request.data)
elif isinstance(request, dict) or isinstance(request, QueryDict):
serializer = self.get_serializer(data=request)

serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

def perform_create(self, serializer):
serializer.save()

def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}

위처럼 수정을 하게 되면 Request가 넘어온 경우에는 request에 포함된 data attribute를 가져옵니다.

그렇지 않을 경우, 즉, Dict나 Query Dict 형태의 POST 데이터가 넘어올 때에는 이것을 Serizlier에게 넘겨주게 됩니다.

그 이후 유효성 검사를 하고 Serializer의 create를 실행하는 절차는 같습니다.

위와 같은 수정이 된다면 이제 커스텀된 POST 데이터 값을 활용하여 부모 클래스의 create() 메서드를 사용할 수 있습니다.

#viewsets.py   
def create(self, request, *args, **kwargs):
self.serializer_class = BusinessUser_Create_Serializer
post_dict = self.request.POST.dict().copy()
hashtag = post_dict.pop('hashtag', None)
if hashtag is not None:
hashtag = json.loads(hashtag) # 문자열을 리스트 형태로 변환한다
post_dict.update({"hashtag": hashtag}) # POST데이터값에 리스트를 포함시킨다
return super().create(post_dict, *args, **kwargs)

위의 예제에서 Request에 포함된 POST 데이터값을 Dict 형식으로 바꾼 뒤 수정하고 있습니다.

위의 과정에서 copy()메서드를 사용한 것을 볼 수 있습니다.

이는 request의 데이터는 기본적으로 수정을 할 수 없습니다. Request를 분석해보시면 기본적으로 Mutable 값이 False로 되어 있는 것을 확인할 수 있습니다. 즉, 변경이 불가능하다는 것이죠.

이를 위해 Request의 POST 데이터값을 복사해와서 복사본을 수정하는 것입니다. 그것을 위해 copy() 메서드를 사용합니다.

위의 예시를 사용할 때 주의할 점이 있습니다.

File이 첨부된 경우에는 위와 같은 방식이 정상적으로 동작하지 않을 수 있습니다.

post_dict = self.request.POST.dict().copy()

위의 메서드는 온전히 POST 필드 데이터값만 가져옵니다.

File들은 위의 사항에 포함되지 않습니다.

File을 함께 처리하고 싶다면 아래의 두가지 방식을 사용합니다.

reqeust의 data 활용하기

request의 data 속성에는 HTTP Multi Part Form으로 보낸 데이터가 모두 포함되어 있습니다.

즉, POST 데이터 필드값, File들 또한 포함되어 있습니다.

Request의 data 속성은 Query Dict 자료형입니다.

request.data.dict()

위의 코드를 통해 Dict 자료형으로 변형할 수 있습니다.

주의해야 할 사항이 있습니다.

request의 data에 파일이 포함되어있는 경우, Dict형으로 변형하게 되면 파일들의 Format도 변해버린다는 것입니다. 이 경우 파일들이 깨져 정상적으로 사용할 수 없습니다.

#viewsets.py   
def create(self, request, *args, **kwargs):
self.serializer_class = BusinessUser_Create_Serializer
data = request.data.dict()
data.update({"new_val":"new"})
return super().create(data, *args, **kwargs)

위의 코드처럼 request의 data에 새로운 데이터를 추가한 후 super 클래스의 create 메서드에 넘기게 되면 Serializer에서 파일들과 POST 필드값 모두 받을 수 있습니다.

request에 포함된 FILES 활용하기

POST 필드값을 Dict 형태로 만든 뒤 File들을 List 형태로 포함시키는 것도 가능합니다.

#viewsets.py   
def create(self, request, *args, **kwargs):
self.serializer_class = BusinessUser_Create_Serializer
post_dict = self.request.POST.dict().copy()
# getlist 메서드를 활용해야만 여러 이미지가 List 형태로 반환됩니다
# 이미지가 없는 경우 빈 List가 리턴됩니다
files = self.request.FILES.getlist('upload_files')
post_dict.update({'files':files})
return super().create(post_dict, *args, **kwargs)


댓글()