[PROJECT] KUKKA CLONING #5

김기현·2022년 3월 13일
0

project_cakoo

목록 보기
5/7
post-thumbnail

주문 API

Order API에서는 장바구니에 담긴 상품을 주문할 때 필요한 API로, uuid를 통한 주문 번호를 생성하고 주문이 중간에 취소되는 등의 상황이 발생할 때는 생성되지 않도록 해야합니다. 기획마다 다르겠지만 주문을 하고 장바구니는 삭제되도록 하였고 주문이 완료되면 주문한 상품을 order_item이라는 테이블 안에 또 담도록 해주었습니다.

주문 창으로 넘어가면 주문내역과 가격, 설명과 수량이 표시되며 주문자 정보는 user의 이름, 전화번호를 가져오도록 되어있습니다.

발신인부터 배송지 정보는 사용자가 입력하도록 되어있으며 꾸까의 해당 페이지를 구현해보겠습니다.

Order POST API

Order Post API코드의 초안은 다음과 같습니다.

urls/config

urlpatterns = [
    path("users", include('users.urls')),
    path("carts", include('carts.urls')),
    path("orders", include('orders.urls'))
] 

orders/urls

from django.urls import path
from .views import OrderView

urlpatterns = [
    path("", OrderView.as_view())

]

orders/view

class OrderView(View):
    @login_decorator
    def post(self, request):
        try:
            data = json.loads(request.body)
            user = request.user
            
            carts        = Cart.objects.filter(user=user)
            uuid_        = uuid.uuid4() 
            order_status = OrderStatus.objects.get(status="Confirmed") 

            with transaction.atomic():
                order = Order.objects.create(
                        user            = user, 
                        sender_name     = user.name,
                        order_number    = uuid_,
                        order_status    = order_status,
                        address         = data["address"],
                        recipient_name  = data["recipient_name"],
                        recipient_phone = data["recipient_phone"],
                    )
            
            order_items = [
                OrderItem(
                    order        = order,
                    product_size = cart.product_size,
                    quantity     = cart.quantity
                ) for cart in carts
            ]     
            OrderItem.objects.bulk_create(order_items)

            Cart.objects.filter(user=user).delete()
            return JsonResponse({"message" : "SUCCESS"}, status=201)

        except KeyError as e:
            return JsonResponse({"message" : getattr(e, 'message', str(e))}, status=401)

{"message" : getattr(e, 'message', str(e))} 코드에서 as e를 사용해 KeyError를 낸 속성에 접근하기 위해서 사용했습니다. 즉 예외가 발생할 때 어떠한 이유로 생긴 것인지 알아보고 편하게 디버깅할 수 있습니다.

carts = Cart.objects.filter(user=user)라는 코드는 한 번의 주문에 Cart에 있는 모든 상품을 무조건 구매하는 조건입니다. 이는 기획의도가 다른 코드이기에 수정해야 합니다.

그리고 위에서 cart를 호출했는데, Cart.objects.filter(user=user).delete() 코드는 cart를 활용하고 있지 않고 있습니다.

마지막으로 다 넘어오면 cart의 상품이 여러개이므로 여러개의 상품이 한번에 들어갈 수 있도록 bulk_create하였습니다.

transaction

with transaction.atomic(): 혹은 @transaction.atomic을 사용한 코드가 있습니다. transaction은 원자성을 보존하기 위해 사용하는데, 예를 들어 돈을 송금하는 과정에서 뒤로가기 버튼을 눌렀거나, 비정상적인 프로세스가 발생하면 송금이 되지 않아야 합니다. 이는 원자성이 보존된 경우로 Order API에서는 사용자가 주문을 취소하거나 비정상적인 프로세스가 있을 때 주문이 생성되지 않아야 합니다.
이에 관한 부분은 해당 블로그를 참조하였습니다.

여기서 가장 큰 오류가 있는데, 지금의 로직에선 transaction을 건 것이 의미가 없습니다. 왜냐하면 order(주문)과 order_items(주문 상품 확인)이 동시에 원자성을 가져야 하는데, order_item이 with문 안에 있지 않기에 작동되지 않습니다. 수정해야 합니다!!

Enum

파이썬 3.4 버전부터 사용가능한 enum은 클래스와 함수로 정의할 수 있는데 대부분 클래스로 정의하게 됩니다. 연관된 상수들의 집합을 의미하는데, Enum을 활용하면 클래스에서 사용하는 대로 사용할 수 있습니다.

from enum import Enum

class OrderStatus(Enum):
    CONFIRMED = 1  
    CANCELED  = 2
    PENDING   = 3

class OrderView(View):
    @login_decorator
    def post(self, request):
        try:
            data = json.loads(request.body)
            user = request.user
            
            cart_ids     = data["cart_ids"]
            carts        = Cart.objects.filter(user=user, id__in=cart_ids)
            order_status = OrderStatus.objects.get(id=OrderStatus.CONFIRMED.value) 

OrderStatus라는 클래스에서 Enum속성을 상속받고 CONFIRMED라는 status는 id가 1번으로, CANCELED는 2번, PENDING은 3번으로 지정하였습니다.

그 후 OrderStatus.objects.get(id=OrderStatus.CONFIRMED.value) 코드에서 주문이 되었을 경우 CONFIRMED된 경우 CONFIRMED의 value가 1번이기에 id=1인 confirmed가 등록됩니다.

import json, uuid
from tkinter.messagebox import CANCEL
from enum import Enum

from django.http   import JsonResponse
from django.views  import View
from django.db     import transaction

from users.utils   import login_decorator
from carts.models  import Cart
from orders.models import Order,OrderItem, OrderStatus

class OrderStatusEnum(Enum):
    CONFIRMED = 1  
    CANCELED  = 2
    PENDING   = 3

class OrderView(View):
    @login_decorator
    def post(self, request):
        try:
            data = json.loads(request.body)
            user = request.user
            
            cart_ids     = data["cart_ids"]
            carts        = Cart.objects.filter(user=user, id__in=cart_ids)
            order_status = OrderStatus.objects.get(id=OrderStatusEnum.CONFIRMED.value) 

            with transaction.atomic():
                order = Order.objects.create(
                        user            = user, 
                        sender_name     = user.name,
                        order_number    = uuid.uuid4(),
                        order_status    = order_status,
                        address         = data["address"],
                        recipient_name  = data["recipient_name"],
                        recipient_phone = data["recipient_phone"],
                )
            
                order_items = [
                    OrderItem(
                        order        = order,
                        product_size = cart.product_size,
                        quantity     = cart.quantity
                    ) for cart in carts
                ]     
                OrderItem.objects.bulk_create(order_items) 
                    
                
            carts.delete()
            return JsonResponse({"message" : "SUCCESS"}, status=201)

        except transaction.TransactionManagementError:
            return JsonResponse({'message':'TransactionManagementError'}, status=400)  
        except KeyError:
            return JsonResponse({"message" : "KEYERROR"}, status=400)


사진에서 보이듯 order_item 테이블에는 주문한 상품의 정보를, order테이블에는 주문정보 등 필요한 정보들이 잘 들어옴을 확인할 수 있습니다.

JsonTypeError

부속으로 Order Get API를 만들다가 나온 에러입니다.이는 order_item.product_size.size에 도달하면 테이블에 도달한 것일 뿐 테이블의 항목을 선택하지 못해 Json TypeError를 내뱉는데 다음의 코드로 해결하였습니다.

profile
피자, 코드, 커피를 사랑하는 피코커

0개의 댓글