24.01.11 - 24.01.12
django rest framework로 미니프로젝트를 진행하며 다중 생성과 업데이트를 구현하게 되었다.
객체를 하나씩 등록하는데에서 오는 피로를 줄이기 위해 한 번에 여러 개체를 등록하고 제거할 수 있는 기능을 만들고자 했다.
DRF에서 동시에 여러 개체를 수정하기 위해서는 ListSerializer를 활용하면 된다
https://www.django-rest-framework.org/api-guide/serializers/#listserializer
Serializers - Django REST framework
www.django-rest-framework.org
공식문서에 따르면 ListSerializer는 여러 개체를 한번에 직렬화하고 유효성을 검사할 수 있다
일반적으로 ListSerializer를 직접 활용하기보다는 many=True를 활용해 serializer 인스턴스를 생성하면 ListSerializer인스턴스가 생성된다고 한다.
옵션으로는 아래 세 가지가 있다.
- allow_empty : boolean값으로 빈 목록 허용/거부
- max_length : defalut = None, 설정한 수량 이상이 목록에 포함되었는지 확인하고 싶을 때 양의 정수로 설정할 수 있다.
- min_length : '수량 이하' 외 max_length와 동일.
CustomListSerializer를 통해 원하는 동작을 추가로 구현할 수 있으며,
아래와 같이 목록화 하고자하는 CustomSerializer 의 Meta class에 ListSerializer를 설정해줌으로써 다중 동작을 정의할 수 있다.
- 한 요소가 목록의 다른 요소와 충돌하지 않는지 확인하는 등의 목록에 대한 특정 유효성 검사를 제공할 때
- 여러 객체의 생성 또는 업데이트 동작을 사용자 정의하려고 할때
class CustomListSerializer(serializers.ListSerializer):
...
class CustomSerializer(serializers.Serializer):
...
class Meta:
list_serializer_class = CustomListSerializer
- 다중 create 커스텀
ListSerializer측에 create를 정의하고 반복문으로 처리해준다.
class BookListSerializer(serializers.ListSerializer):
def create(self, validated_data):
books = [Book(**item) for item in validated_data]
return Book.objects.bulk_create(books)
class BookSerializer(serializers.Serializer):
...
class Meta:
list_serializer_class = BookListSerializer
- bulk_create()
django의 query.pyi 파일의 _BaseQuerySet에 정의되어있다
- save함수는 DB와 connection하여 insert구문을 수행한다.
save()로 다수의 레코드를 반복 입력하게 되면, DB와의 커넥션이 여러 번 발생하게 되어 큰 부하를 발생시킬 수 있다.
따라서 한 번의 연결을 통해 여러 번의 insert, update를 수행하는 것이 효율적인데
이를 실행할 수 있도록 해주는 함수가 bulk_create 함수이다. - 주의할 점
bulk 동작을 통해 create나 update를 수행할 때는 save(), clean() 등의 기본 메서드를 사용할 수 없다.
이에 따라 DB에 유효하지 않은 값이 들어가거나 데이터 무결성이 깨질 가능성이 생긴다.
따라서 bulk를 사용할 때는 해당 모델의 생성에 의해 영향을 받는 모델이 있는지, 혹은 모든 필드에 유효한 값이 들어오는지 등 세심한 전처리가 필요하다. ( = 유효성 검사를 직접 작성해야함:).. )
- 다중 update 커스텀
기본적으로 ListSerializer는 다중 update 을 지원하지 않는다고 한다.
삽입과 삭제에서 예상되는 동작이 모호 = 생성에 비해 여러가지 동작이 가능하기 때문이다.
따라서 고려해야할 사항이 여러가지 있는데,
1) 어떤 인스턴스를 업데이트 할 것이고 어떻게 결정할 것인지
2) 추가 데이터에 대한 update 처리를 허용/거부할 것인지, 새로운 객체를 생성할 것인지
3) 제거 시 객체를 삭제할 것인지, 관계를 제거할 것인지, 혹은 무시할 것인지
4) 두 항목의 위치 변경이 의미하는 동작은? 상태변경 / 무시 등등..
여러 업데이트를 구현할 시, 변화가 없고 unique한 필드를 먼저 명시적으로 설정한다.
아래 예시에서는 'id' 필드를 명시적인 필드로 추가하였고, 이때 id 필드는 암묵적으로 read_only로 취급된다.
class BookListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
# Maps for id->instance and id->data item.
book_mapping = {book.id: book for book in instance}
data_mapping = {item['id']: item for item in validated_data}
# Perform creations and updates.
ret = []
for book_id, data in data_mapping.items():
book = book_mapping.get(book_id, None)
if book is None:
ret.append(self.child.create(data))
else:
ret.append(self.child.update(book, data))
# Perform deletions.
for book_id, book in book_mapping.items():
if book_id not in data_mapping:
book.delete()
return ret
class BookSerializer(serializers.Serializer):
# We need to identify elements in the list using their primary key,
# so use a writable field here, rather than the default which would be read-only.
id = serializers.IntegerField()
...
class Meta:
list_serializer_class = BookListSerializer
bulk operation 적용 전 최종 정리.
1) create를 사용하면 개별 validation이 가능하지만 대량의 데이터가 들어올 시 부하가 생김
-> 로컬 환경에서 대량의 데이터를 등록할 일이 있을까? 고려하기
2) bulk create를 사용하면 개별 객체의 validation이 불가능 함
-> 데이터를 전처리 해서 넣어주는 방법은 없을까? 고려하기
-> 백엔드단에서 validation을 적용한다면.. 개별 객체를 validation하고 bulk create를 적용할 순 없을까?
: 개별 객체에 대한 full_clean()에서 호출과 bulk_create() 연산이 분리되어있어 성능상 이점이 딱히 없을 수 있음..
일반적인 create가 나을 수 있다고 한다.
초기 계획과 목적에 따라 full_clean()으로 개별 유효성 검사를 진행하고 bulk_create를 활용해 구현하기로 했다.
# serializers.py
class CustomStockMultipleListSerializer(serializers.ListSerializer):
def create(self, stock_list_id, validated_data):
stocks = []
failed_objects = []
for item in validated_data:
stock = StockInterested(stock_list_id=stock_list_id, **item)
if not stock.full_clean():
failed_objects.append(item['code'])
continue
stocks.append(stock)
StockInterested.objects.bulk_create(stocks)
return failed_objects
class StockMultipleCreateSerializer(serializers.ModelSerializer):
"""StockInterested 다중 생성을 위한 custom serializer"""
class Meta:
model = StockInterested
fields = ['market_code', 'code',]
list_serializer_class = CustomStockMultipleListSerializer
#views.py
class StockListDetailView(APIView):
def post(self, request, stock_list_id):
stock_list = get_object_or_404(StockList, id=stock_list_id)
failed_objects = StockMultipleCreateSerializer(stock_list_id, data=request.data, many=True)
if failed_objects:
return Response({"message": "Some objects failed validation", "failed_objects": failed_objects}, status=status.HTTP_400_BAD_REQUEST)
return Response({"message": "All objects has been created successful"}, status=status.HTTP_201_CREATED)
bulk_create를 사용하면 db에 바로 작업이 되기때문에
개별 serializer를 작업할 때처럼 is_valid() -> save()를 진행하지 않아도 된다.
따라서 failed_objects 만 return하여 실패한 데이터가 존재할 경우 해당 아이템을 확인할 수 있도록 Response처리하였다.
[참고] :
https://www.django-rest-framework.org/api-guide/serializers/#listserializer
https://powerlichen.github.io/posts/drf-bulk-update/
'AI 웹개발반 > Python, Django' 카테고리의 다른 글
[DRF] APIView와 exception handler (2) (0) | 2024.01.26 |
---|---|
[DRF] APIView와 exception handler (1) (0) | 2024.01.16 |
[TIL] 코드 리팩토링 Code Refactoring (0) | 2023.07.07 |
[Django] datetime compare 오류, naive와 aware (0) | 2023.07.06 |
[Django] JSONfield (0) | 2023.07.04 |