본 작성자는 아직 1년차도 못채운 햇병아리 코더입니다.
(사내에 장고를 다루는 사람이 저 혼자라서) 기술한 내용중에 틀린 내용이 있을 수도 있습니다.
예제랍시고 올려놓은 코드가 아주 허접할 수도 있습니다.
미숙한 제게 조언을 주실 수 있다면 댓글 부탁드립니다. 겸허히 배우겠습니다.
개발 대상 사양은 모두 아래를 공통적으로 사용합니다.
[개발 OS : Windows10]
[설치 OS : CentOS 8 with gnomeUI]
[Python: 3.8]
[Mongodb : 4.4.1]
[PostgreSQL : 12.4]
[Elasticsearch : 7.9.3]
[Tensorflow : 2.4.1]
[scikit-learn : 0.23.2]
------------------------------------------------------------------
이전 챕터에서 아래와 같은 모델을 만들어서 사용했었습니다.
<ocrapp/models.py>
from djongo import models
class PreprocessedImages(models.Model):
image_id = models.AutoField(primary_key=True) # 나중에 수정할 부분입니다!
image_title = models.TextField(null=True, default="a.jpg")
image_path = models.FilePathField(null=False, default="/home/fffcoder/ocrandnlp/images/a.jpg")
image_context = models.TextField(null=False, default="일방통행")
class Meta:
db_table = "preprocessed_images"
몽고DB는 기본적으로 모든 bson 파일에 '_id' field 를 만들고, 이를 키값으로 사용합니다.
그런데, 위와 같은 모델로 데이터를 하나 생성해보면, 자동으로 '_id' field가 생성되고 ObjectID가 됨을 알 수 있습니다.
위 구조대로라면 키값이 2개가 되는 비효율적인 구조가 되는 것입니다.
먼저 이 부분부터 처리하도록 하겠습니다.
<ocrapp/models.py>
from djongo import models
class PreprocessedImages(models.Model):
_id = models.ObjectIdField()
# image_id = models.AutoField(primary_key=True)
image_title = models.TextField(null=True, default="a.jpg")
image_path = models.FilePathField(null=False, default="/home/fffcoder/ocrandnlp/images/a.jpg")
image_context = models.TextField(null=False, default="일방통행")
class Meta:
db_table = "preprocessed_images"
어차피 '_id' 가 자동으로 생성되므로 image_id 필드는 필요가 없습니다.
대신, ' 자동 생성될 '_id' 필드를 명시해줌으로써 장고에 이를 사용하겠다 알려야 합니다. primary_key 속성은 굳이 명시하지 않아도 됩니다.
아래는 djongo의 내장 코드입니다만 이미 primary_key가 선언되어 있습니다.
class ObjectIdField(_ObjectIdField):
"""
For every document inserted into a collection MongoDB internally creates an field.
The field can be referenced from within the Model as _id.
"""
def __init__(self, *args, **kwargs):
id_field_args = {
'primary_key': True,
'auto_created': True
}
id_field_args.update(kwargs)
super().__init__(*args, **id_field_args)
이제 이 데이터 모델의 pk는 _id 입니다.
그런데 몽고DB의 ObjectID는 12byte의 이진 데이터이고, djongo의 기반이 되는 pymongo에서는 ObjectID라는 객체로 따로 관리하게 됩니다.
백문이 불여일견, 일단 데이터를 하나 만들어 봅시다.
http://localhost:8000/ocr/preprocessed_image/
위 url로 body가 비어있는 post 요청을 하나 날려보시면, 모델의 모든 필드에 기본값이 설정되어 있기 때문에 데이터 하나가 만들어 질 것입니다.
이제 우리는 _id의 값이 "604327b5663f459082617097" 이라는 것을 알았습니다. 하지만 이 값은 엄밀히 따져 진짜 ObjectID 객체의 값을 나타내지는 않습니다.
GET 요청을
http://localhost:8000/ocr/preprocessed_image/604327b5663f459082617097/
위 url로 날려보면 당연히 not found 로 리턴값이 돌아오게 됩니다.
ObjectID 객체는 따로 처리를 해줄 필요가 있습니다.
기본적으로 DRF(Django Rest Framework)의 router는 각각의 HTTP 메서드가 ModelViewSet의 메서드에 1대1로 매칭되어 있습니다.
<Detail View>
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
<List View>
'get': 'list',
'post': 'create'
만약 어떤식으로든 각 메서드의 처리를 달리해야할 필요가 있다면 해당 메서드를 오버라이딩하는 것으로 그 처리를 담당하게 할 수 있습니다.
이번 경우에는 retrieve 함수를 수정해야합니다. retieve 함수의 경우에는 RetrieveModelMixin 클래스에서 상속받은 메서드입니다. 내용은 아래와 같고 여기에 다양한 함수를 커스텀할 수 있습니다.
<mixin.py>
class RetrieveModelMixin:
"""
Retrieve a model instance.
"""
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
위 코드를 베껴서 실제의 코드에 적용해봅니다.
<ocrapp/views.py>
from rest_framework.viewsets import ModelViewSet
from rest_framework.response import Response
from ocrapp.serializers import PreprocessedImageSerializer
from ocrapp.models import PreprocessedImages
from bson import ObjectId
class PreprocessedImageViewSets(ModelViewSet):
queryset = PreprocessedImages.objects.all()
serializer_class = PreprocessedImageSerializer
def retrieve(self, request, *args, **kwargs):
self.kwargs["pk"] = ObjectId(self.kwargs["pk"])
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
이때, get_object() 함수에서는 pk를 self.kwargs에서 가져오므로 str로 되어있는 kwargs["pk"]를 ObjectId 로 바꾸어주면 됩니다.
(bson 모듈은 djongo를 설치하는 과정에서 자동 설치되어 있습니다.)
이제 테스트를 해보면 url에 ObjectId를 넘겨서 처리할 수 있습니다.
GET 방식을 처리했으니 같은 방식으로 다른 메서드들도 오버라이딩 해주면 됩니다.
다만 위 방식으로 처리하는 것은 비효율적이니 데코레이터를 하나 만들어서 처리합니다.
<ocrapp/decorators.py>
from bson import ObjectId
def request_converting_to_object_id(func):
def object_id_insert(*args, **kwargs):
args[0].kwargs["pk"] = ObjectId(kwargs["pk"])
response = func(*args, **kwargs)
return response
return object_id_insert
<ocrapp/views.py>
from rest_framework.viewsets import ModelViewSet
from ocrapp.serializers import PreprocessedImageSerializer
from ocrapp.models import PreprocessedImages
from ocrapp.decorators import request_converting_to_object_id
class PreprocessedImageViewSets(ModelViewSet):
queryset = PreprocessedImages.objects.all()
serializer_class = PreprocessedImageSerializer
@request_converting_to_object_id
def retrieve(self, request, *args, **kwargs):
return super().retrieve(self, request, args, kwargs)
@request_converting_to_object_id
def partial_update(self, request, *args, **kwargs):
return super().partial_update(self, request, args, kwargs)
@request_converting_to_object_id
def update(self, request, *args, **kwargs):
return super().update(self, request, args, kwargs)
@request_converting_to_object_id
def destroy(self, request, *args, **kwargs):
return super().destroy(self, request, args, kwargs)
메서드는 더 있지만, pk를 활용하는 메서드는 이 4개이므로 일단 이것만 수정해주면, ObjectID를 하나의 pk로 사용할 수 있게 됩니다!
여기까지 진행된 시점에서 잠깐 serializer도 짚고 넘어가겠습니다.
분명 요청은 처리가 되어있는데 응답부분이 처리가 안되어 있지 않나요?
이전시간에서는 image_id를 read_only_field로 돌려두었습니다. 그리고 이걸 "_id" 로 변경한다 한들 아직 출력되지는 않지요.
<ocrapp/serializers.py>
from rest_framework import serializers
from ocrapp.models import PreprocessedImages
class PreprocessedImageSerializer(serializers.ModelSerializer):
class Meta:
model = PreprocessedImages
# fields = '__all__'
fields = ["image_title", "image_context", "_id"]
# read_only_fields = ["image_id"]
일단, API가 ObjectID도 반환하도록 serializer를 바꾸어 보겠습니다.
이때, djongo와 DRF의 시너지가 여기서 발휘됩니다.
반환받은 "_id" 는 단순한 스트링이기 때문에 이렇게 내뱉은 key값을 나중에 다시 받을 수도 있게 됩니다!
이제 이 API는 몽고DB의 기본 제공 id값인 ObjectId로 CRUD 작업을 할 수 있게 됩니다.
참고
https://velog.io/@jcinsh/RetrieveUpdateDestroyView-%EC%9D%B4%ED%95%B4
'Django and Mongo' 카테고리의 다른 글
Django REST API & Djongo - 5. CustomSerializer (0) | 2021.03.27 |
---|---|
Django REST API & Djongo - 4. CustomField & Embedded Model (0) | 2021.03.22 |
Django REST API & Djongo - 3. Image Upload & Download (0) | 2021.03.15 |
Django REST API & Djongo - 1. Introduce (0) | 2021.03.06 |