user가 장바구니에 담은 정보를 보여주는 기능을 구현하기 위해,
get
메소드를 이용하여 아래와 같이 작업하였다.
...
FREE_SHIPPING = 20000
...
class CartsView(View):
@login_required
def get(self, request):
user = request.user
cart_list = [{
'id' : item.id,
'product_name' : item.product.name,
'quantity' : item.quantity,
'size' : item.option.size,
'product_price': item.option.price,
'image_url' : item.product.thumbnail_image_url,
'price' : int(item.option.price) * int(item.quantity)
} for item in Cart.objects.filter(user=user)]
total_price = Cart.objects.filter(user=2).aggregate(
total=Sum(F('option__price')*F('quantity'))['total'] or 0
shipping_price = 0 if total_price > FREE_SHIPPING or not total_price else 2500
return JsonResponse({
"Cart" : cart_list,
"shipping" : shipping_price,
"total_price": total_price
}, status=200)
우선 일정 금액 이상이면 배송비를 무료로 하는데,
일정 금액(FREE_SHIPPING)을 상수화 하여 값을 지정하였다.
그리고 total_price
는 aggregate
를 사용하여,
해당하는 Cart객체의 상품 가격과 수량을 곱한 다음 전부 더해주었다.
{ 'total' : total_price } # 해당 장바구니의 총 가격
하지만 이렇게 되면 장바구니가 비어있는 유저에는 가격과 수량이 존재하지 않아,
변수에 None값이 출력되기에 or 0
을 추가하여 0으로 지정되게 하였다.
이럴 경우에 사용하는 django 메소드가 있는데, 그것이 바로 Coalesce
이다.
Coalesce?
django 공식문서에서는 아래와 같이 설명하고 있다.
Coalesce는 둘 이상의 필드 이름 또는 표현식 목록을 승인하고 널이 아닌 첫 번째 값을 리턴합니다. (빈 문자열은 널값으로 간주되지 않음)
즉, 차례로 값을 확인하여 null값이 아닌 값을 지정한다는 뜻이다.
그래서 이 방법을 사용하여 아래와 같이 실행하였다.
>>> from django.db.functions import Coalesce
>>> from django.db.models import Sum,F
>>> from carts.models import Cart
>>>
>>> total_price = Cart.objects.filter(user_id=2).aggregate(
total=Coalesce(Sum(F('option__price')*F('quantity')),0))['total']
하지만 이렇게 실행하였을때, 값이 지정되지 않고 에러가 발생하였다.
그 이유를 찾아보니 위에서 보던 공식문서에 자세한 부분을 살펴보면,
...Each argument must be of a similar type,
so mixing text and numbers will result in a database error.
각각의 타입형이 다르면 연산이 되지 않기 때문에 주의해야 한다고 쓰여있다.
(이래서 자세히 읽어봐야 한다.)
option__price
가 Decimal type으로 지정되어 있고,
quantity
와 0
이 Integer type이기 때문에 연산에서 에러가 발생한 것이다.
django에서는 결합하려는 필드의 유형이 다른 경우 어떤 종류의 필드가 반환되는지 알려야 한다.
그래서 해결한 방법이 output_field
를 설정해주는 것이다.
>>> from django.db.functions import Coalesce
>>> from django.db.models import Sum,F, IntegerField
>>> from carts.models import Cart
>>>
>>> total_price = Cart.objects.filter(user_id=2).aggregate(
total=Coalesce(Sum(F('option__price')*F('quantity')), 0, output_field=IntegerField()))['total']
output_field
에 IntegerField
를 지정하면,
django에게 필드의 유형을 알려주게 되면서 해결이 가능하다.
참조
(https://docs.djangoproject.com/en/3.2/ref/models/database-functions/)
(https://stackoverflow.com/questions/38546108/django-aggregation-expression-contains-mixed-types-you-must-set-output-field)