될때까지

((TURTLE HOME)) 6일차 : ProductListView 1/2 본문

프로젝트/wecode1차 : TURTLE-HOME

((TURTLE HOME)) 6일차 : ProductListView 1/2

랖니 2022. 7. 23. 12:57
728x90

첫번째 시도

이제 상품 전체 리스트를 보여주는 API를 만들 차례다. 자라홈은 상품목록을 보여줄 때, 최저가와 최고가 2가지의 가격이 나타난다. 백엔드에서 프론트로 가격을 최저가, 최고가만 보내줄까 아니면 연결되어있는 모든 옵션에 대한 가격을 다 보내줄까 고민을 했는데 '가격필터'를 적용시키기 위해서는 모든 가격을 다 주는 게 좋을 것 같다는 판단이 들었다. 상품 테이블에는 가격이 없어서 상품옵션테이블과 연결을 해야하는데 내 생각처럼 바로 작동하지 않아서 시간이 좀 걸렸다.

음 갓벽해 하고 서버를 실행시키고 테스트를 했는 데?

응 반가워 500 ^_^ 이상하다 역참조 개념을 사용해서 _set을 사용했으면 ProductOption 테이블에 연결이 되었으니까 테이블의 컬럼 중 하나인 price를 가져올 수 있을 것 같은데? price라는 속성값이 없다니 이게 무슨 일일까 ...? 분명히 뭔가 사소한 하나를 놓쳤을꺼다 한시간넘게 모니터를 째려봤는데 머리가 굴러가질 않았다. 이럴 때 위코드 최고 함께해서 위코드 도와주세요 동기님!!

현재 ProductOption테이블에는 product_id가 (예를 들어) 1인 객체가 1개만 있는 게 아니라 서너개가 들어가있다. 그러니까 당연히 .price를 하면 값을 가져오는 게 안되는게 아닐까 의견을 주셨다. 듣고 보니 맞네.!!! 해당 테이블에 객체가 하나만 있는 게 아니라 여러개가 존재하기 때문에 .price를 할 수 없다. 그렇다면 어떻게 ProductOption의 객체 하나씩 가져와서 해당 price를 추출할 수 있을까

쉘에서 테스트를 해보자.

>>> p = Product.objects.all()
>>> p
<QuerySet [<Product: Product object (1)>, <Product: Product object (2)>, 
<Product: Product object (3)>, <Product: Product object (4)>, 
<Product: Product object (5)>, <Product: Product object (6)>, 
<Product: Product object (7)>, <Product: Product object (8)>, 
<Product: Product object (9)>, <Product: Product object (10)>, 
<Product: Product object (11)>, <Product: Product object (12)>, 
<Product: Product object (13)>, <Product: Product object (14)>, 
<Product: Product object (15)>, <Product: Product object (16)>, 
<Product: Product object (17)>, <Product: Product object (18)>, 
<Product: Product object (19)>, <Product: Product object (20)>, 
'...(remaining elements truncated)...']>

p에는 Product 객체들이 담겨있다. 테스트 삼아 p의 첫번째에 price를 구해보려면.. 일단 쿼리셋에 담겨있는 1번째 값을 추출하고, 1번에 연결되어있는 ProductOption들을 가져오자

>>> a = p[0].productoption_set.all()
>>> a
<QuerySet [<ProductOption: ProductOption object (1)>, 
<ProductOption: ProductOption object (2)>, 
<ProductOption: ProductOption object (3)>, 
<ProductOption: ProductOption object (4)>, 
<ProductOption: ProductOption object (5)>]>

오우케이. 담겼어..? 또 쿼리셋이니까 여기서 테스트를 한다면, a[0].price하면 가격이 나올 것 같다. 과연 결과는

>>> a[0].price
Decimal('169000.00')

🔥오케이 성공이다!!🔥

그렇다면 이제 이 코드를 내 코드에 반영하면 된다.. 코드를 수정해보자

두번째 시도

서버를 돌렸더니 또 뜨는 500에러. 500에러는 내가 어떤 작업을 하고 있는 지 명확히 알고 실행할때는 무섭지 않다. 더이상 쫄지않아!!

반가워 500

쿼리셋은 JSON 직렬화할 수 없다. 직렬화의 뜻을 찾아봤는데 간단하게 말하자면 데이터들을 통신에서 사용할 수 있게 값(Value) 형식 데이터로 변환이 되야하는데 그게 안되어있기 때문에 못쓴다는 뜻인 것 같았다. 프린트문을 사용해서 찍어봤더니

        print([product.productoption_set.filter(product_id = product.id)  for product in products])

아-하. 지금 filter를 사용해서 연결된 모든 상품옵션들을 가져왔으니까 하나가 아닌 쿼리셋으로 가격들이 담겨있기 있으니까 객체를 하나하나 뽑기 위한 작업이 필요했다.

세번째 시도

        products = Product.objects.all()
            
        result = [{ 'id'       : product.id, 
                    'name'     : product.name,
                    'image_url': product.image_url,
                    'prices'   : product.productoption_set.filter(product_id = product.id)[0]} for product in products]

        return JsonResponse({'result':result}, status=200)

쿼리셋안에는, 상품 옵션 테이블에서 상품 id가 일치하는 객체들이 쿼리셋으로 담겨있다. 쿼리셋은 리스트와 비슷하니까 인덱스개념을 사용할 수 있다. 실제로 프린트문을 사용해서 찍어보니 제대로 찍혔다..!! 그럼 이제 여기서 [0]을 계속 증가시켜주면 된다. 언제까지? 상품옵션테이블에 걸려있는 상품id를 다 가져올 때 까지. 반복문이니까 for문이나 리스트 컴프리헨션을 사용하면 가능하다는 생각이 들었다. 여기서 저 [0]을 어떻게 i로 바꿔줄 수 있을까

        products = Product.objects.all()   # 전체 상품 객체들을 가져온다 => 여러개니까 products에 쿼리셋이 담김 => 반복문을 돌며 담기
            
        result = [{ 'id'       : product.id, 
                    'name'     : product.name,
                    'image_url': product.image_url,
                    'prices'   : [product.productoption_set.filter(product_id = product.id)[i].price for i in range(len(product.productoption_set.filter(product_id = product.id)))]} for product in products]

        return JsonResponse({'result':result}, status=200)

결과는 담겼다!!!!!!

result = [{ 'id'       : product.id, 
            'name'     : product.name,
            'image_url': product.image_url,
            'prices'   : [product.productoption_set.filter(product_id = product.id)[i].price for i in range(len(product.productoption_set.filter(product_id = product.id)))]} for product in products]

ㅋㅋㅋㅋㅋㅋ 아름답다.. 드디어 담겼다. 하지만 어마무시한 길이의 prices 그래도 기쁘다 뽑아왔다니. 이제 이 더럽고 지저분한 코드를 줄여보자. 이번에도 동기님의 도움을 받았고, 저 i가 마음에 안든다면서 멋지게 p를 대체사용하여 코드를 줄여줬다!!!

네번째 시도

        products = Product.objects.all()  
            
        result = [{ 'id'       : product.id, 
                    'name'     : product.name,
                    'image_url': product.image_url,
                    'prices'   : [p.price for p in product.productoption_set.filter(product_id = product.id)]} for product in products]
            
        return JsonResponse({'result':result}, status=200)

 

짜잔 드디어 성공 ㅠㅜ 밤 11시가 다되서 해결을 했다 히히힣 혼자의 힘으로는 해결을 못했지만, 동기들의 설명을 들으며 내 머릿속에 개념을 쏙쏙 집어넣었다. 감사합니도!!

가격이 하나인 상품도 리스트에 담겨서 반환한다

리스트 컴프리헨션을 사용해서 가격이 1개인 경우에도 리스트에 담아 반환된다. 깨끗이 코드를 정리하면 아래와 같다.

class ProductListView(View):
    def get(self, request):
        
        products = Product.objects.all()   
            
        result = [{ 'id'       : product.id, 
                    'name'     : product.name,
                    'image_url': product.image_url,
                    'prices'   : 
                        [p.price for p in product.productoption_set.filter(product_id = product.id)]
                    } for product in products] 

        return JsonResponse({'result':result}, status=200)

 

아래의 코드는 다른 동기분이 _ _ 를 사용하는 방법도 있다고 알려주셨다. 상당히 신기한 개념이였는데 공부하고 싶어서 여기에 남겨놓고 넘어간다!

# 'price' : [p.price for p in product.objects.filter(productoption__product_id=product.id).price]

 

자. 전체 상품 목록을 보여주는 건 다 했으니까.. 이제 정렬/필터/페이지네이션이 남았군.

728x90