될때까지

((TURTLE HOME)) 7일차 : ProductListView ordering 2/2 본문

프로젝트/wecode1차 : TURTLE-HOME

((TURTLE HOME)) 7일차 : ProductListView ordering 2/2

랖니 2022. 7. 24. 13:59
728x90

이제 상품 리스트를 정렬할 차례다. 자라홈에서 정렬은 낮은 가격순 / 높은 가격순 / 최신 등록순이 있다.

1. 최신 등록순

class ProductListView(View):
    def get(self, request):

        sorting = request.GET.get('sort-by')   
        # GET은 request를 딕셔너리로 받는다.
        # get메소드는 해당 키값에 대한 벨류값을 가져오며, 없을 경우 None을 리턴한다.

        # 정렬은 중복선택 안됨 -> 낮은가격/높은가격/최신에 따른 분기처리 각각하기

        # 낮은 가격순 => price
        if sorting == 'price':
            pass

        # 높은 가격순 => -price
        elif sorting == '-price':
            pass

        # 최신 등록순 => -id
        elif sorting == '-id':
            products = Product.objects.all().order_by('-id')

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

            return JsonResponse({'message':'Ordering by newest', 'result':result}, status=200)

        # 노정렬(기본값)
        products = Product.objects.all()   

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

처음 작성한 코드는 위와 같다. 일단 정렬은 path parameter가 아닌 query parameter가 적합하다. 클라이언트가 선택한 정렬기준을 어떻게 받아서 적용할 수 있을까 궁금했는데, request.GET.get()을 사용하면 된다고 한다.

받은 값을 sorting이라는 변수에 저장한 뒤, 해당 변수가 price(낮은 가격순) / -price(높은 가격순) / -id(최신 등록순)에 따라 각각 분기처리를 해줬다. 최신 등록순은 처음에는 등록한 순서대로 id값이 부여되니까, -id를 사용함으로써 구현했었다. 하지만 다시 생각해보니 우리팀은 최신등록순 기능을 구현하기 위해 Product 모델에 created_at이라는 필드를 나중에 추가해줬다. 해당 값을 사용하고 싶어서 id가 아닌 created-at을 사용하기로 수정했다.

	products = Product.objects.all().order_by('-created_at')

그렇게 최신등록순은 완료 🔥

 

2. 낮은 가격순

하 정말 하루를 다 투자했다. ^___^ 일단 가격을 정렬해서 프론트에 보내줄 때, 상품안의 가격 리스트들을 정렬해서 보내줘야할까? 근데 그게 가능한가? 상품 데이터에 있는 가격 리스트의 값을 기준으로..? 리스트안의 , 딕셔너리안의, 리스트안의 값을 기준으로 정렬? 너무 어렵다.

그렇다면 다른 어떤 방법이 있을까. Product에 가격 리스트를 붙이지 말고, ProductOption에 price가 있으니까 Product 이름을 붙이면 되는 거 아닐까? 예전에는 프론트와 상의없이 일단 구현하고 난 뒤에 이야기를 했다. 하지만 과연 내가 이렇게 주면(상품에 가격들을 리스트로 담아서 보내는 게 아니라, 가격리스트들에 상품정보들을 붙여버리면 가격묶음이 안생긴다.) 프론트가 편할까? 프론트는 어떻게 보내줘야 좋을까 그리고 어떻게 생각하는지 의견을 먼저 듣고 로직을 작성했다. 우리는 협업이니까.! 서로가 편한 방식으로 보내주고 받아야 성공적인 통신이 된다. 

프론트에게 물어봤더니, 상품안에 가격정보들을 리스트로 담아서 주는 게 좋을 것 같고 정렬같은 경우도 다 쪼개지말고 상품안의 가격 리스트 중 최소가를 기준으로 정렬하면 어떨까 의견을 말해줬다. 듣고보니 그렇게 줘야하는 게 맞다는 생각이 들었고 이제 기능 구현에 들어갔다.

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

from products.models import Product
    
class ProductListView(View):
    def get(self, request):
        
        sort_condition = request.GET.get('sort-by')   
        
        # 낮은 가격순 => price
        if sort_condition == 'price':
            # sort_by_lowprice = ProductOption.objects.all().order_by('price')  그러면 Product의 이름, 이미지정보가 없다
            # a = [t for t in sort_by_lowprice]
            # print(a)
            
            # print([product.productoption_set.filter(product_id = product.id) for product in products])  # 상품과 가격이 연결되었음
            # test = [product.productoption_set.filter(product_id = product.id) for product in products]
            # test = products.annotate(price = ProductPrice('productoption')).order_by('-price')
            
            # test = ProductOption.objects.values('price', 'product__id', 'product__name', 'product__image_url')
            # print(test)    => 1개의 상품에 4개의 가격이 나와야하는데 가격이 모두 다르니까 4개의 객체가 생성된다.
            
            # ----- 1번째 방법 : 상품옵션 객체를 하나하나 다 쪼갰고 거기에 이름을 붙여서 보내주고 있음
            # product_options = ProductOption.objects.annotate(name=F('product__name'), image_url=F('product__image_url'), p_id=F('product__id')).order_by('-price')  # 상품이름과 옵션테이블 연결하기
            # result          = [{ 'product_id' : product.p_id, 
            #                      'name'       : product.name,
            #                      'image_url'  : product.image_url,
            #                      'price'      : [product.price ]} for product in product_options]

            # return JsonResponse({'message':'Ordering by highest price', 'result':result}, status=200)
            
            # ----- 2번째 방법 : 하나의 상품안에 여러가지 가격들이 담겨있다. 그 가격들은 정렬되어 있음. 그 정렬된 가격들 중에서 [0]기준 [-1]기준으로, 그 값들을 정렬해서 보내주자.
            products =  Product.objects.all()
            
            result = [{ 'id'       : product.id, 
                        'name'     : product.name,
                        'image_url': product.image_url,
                        'prices'   : 
                            [int(p.price) for p in product.productoption_set.filter(product_id = product.id)]
                        } for product in products] 
        
            sorted_result = sorted(result, key = lambda x : x['prices'][0])
        
            return JsonResponse({'message':'Ordered by lowest price', 'result':sorted_result}, status=200)

 

^___^ 상당히 긴 주석처리. 처음에 상품테이블과 옵션테이블의 가격을 연결하는 부분에서 해맸는데 가만 생각해보니까 이 전에 만들었던 result를 정렬하면 되잖아!? 그럼 이제 그 result를 가지고, prices 리스트 안에 있는 첫번째 가격을 기준삼아 어떻게 정렬할까가 관건이였다. 예시 코드를 만들어서 테스트했었다.

a_list = [  {'name' : '꽃무늬 이불 커버', 
             'prices' : [4, 5, 8, 10]},
            {'name' : '도트무늬 이불 커버', 
             'prices' : [1, 4, 5, 11]},
            {'name' : '스프라이트 패턴 이불 커버', 
             'prices' : [2, 6, 9]}]

# 낮은 가격순 정렬 => 도트무늬, 스프라이트, 꽃무늬가 나와야한다.

# 첫번째 시도
# 왜 안돼에에?
print(a_list.sort(key=lambda x:x['prices'][0] for x in a_list))

# 안되는 이유 : for x in a_list가 불필요한 코드였다.
a_list = [  {'name' : '꽃무늬 이불 커버', 
             'prices' : [4, 5, 8, 10]},
            {'name' : '도트무늬 이불 커버', 
             'prices' : [1, 4, 5, 11]},
            {'name' : '스프라이트 패턴 이불 커버', 
             'prices' : [2, 6, 9]}]

# 두번째 시도
# 왜 또 안돼에에?
# print(a_list.sort(key=lambda x:x['prices'][0]))  # None 반환

# sort는 기존리스트를 정렬하기 때문에 리턴값이 None이다.
a_list.sort(key=lambda x:x['prices'][0])
print(a_list)

자꾸 헷갈리는 sort와 sorted.   코드를 수정하고 돌려보니 원하는 결과값대로 정렬이 잘 됐다.

prices의 첫번째 값을 기준으로 도트무늬/스프라이트/꽃무늬가 출력됨

해당 코드를 자라홈 프로젝트에 반영시켜서 낮은 가격순 정렬을 완성했다. 

 

3. 높은 가격순

0번째로 정렬했으니, -1로 정렬하면 된다 생각했지만, 내 생각대로 잘 출력되지 않아서 여기서도 시간을 많이 소모했다.

도트/꽃무늬/스트라이트가 나와야하는데 출력값을 보니 9,10,11 -1번째 값을 기준으로 오름차순으로 정렬이 되었다. 역순으로 정렬하고 싶었다면 reverse=True를 넣어줬으면 해결인데 어제는 왜 그렇게 생각이 안나던지 ㅜㅜ!!!! 

자라홈 코드에 반영시켜보자

from django.http      import JsonResponse
from django.views     import View
from django.db.models import F

from products.models import Product, SubCategory
    
class ProductListView(View):
    def get(self, request):
        
        sort_condition = request.GET.get('sort-by')   # get메소드는 해당 키값에 대한 벨류값이 없을 경우 None을 리턴한다
        
        # 정렬은 중복선택 안됨 -> 낮은가격/높은가격/최신에 따른 분기처리 각각하기
        
        # 낮은 가격순 => price
        if sort_condition == 'price':
           
            products =  Product.objects.all()
            
            result = [{ 'id'       : product.id, 
                        'name'     : product.name,
                        'image_url': product.image_url,
                        'prices'   : 
                            [int(p.price) for p in product.productoption_set.filter(product_id = product.id)]
                        } for product in products] 
        
            sorted_result = sorted(result, key = lambda x : x['prices'][0])
        
            return JsonResponse({'message':'Ordered by lowest price', 'result':sorted_result}, status=200)
        
        # 높은 가격순 => -price
        elif sort_condition == '-price':
            products =  Product.objects.all()

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

            sorted_result = sorted(result, key = lambda x : x['prices'][0], reverse=True)

            return JsonResponse({'message':'Ordered by highest price', 'result':sorted_result}, status=200)
        
        # 최신 등록순 => -id
        elif sort_condition == '-id':
            products = Product.objects.all().order_by('-created_at')
            
            result = [{ 'id'       : product.id, 
                        'name'     : product.name,
                        'image_url': product.image_url,
                        'prices'   : 
                            [int(p.price) for p in product.productoption_set.filter(product_id = product.id)]
                        } for product in products] 
            
            return JsonResponse({'message':'Ordered by newest', 'result':result}, status=200)
        
        # 일반
        products = Product.objects.all()   
            
        result = [{ 'id'       : product.id, 
                    'name'     : product.name,
                    'image_url': product.image_url,
                    'prices'   : 
                        [int(p.price) for p in product.productoption_set.filter(product_id = product.id)]
                    } for product in products] 

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

postman으로 테스트해보니 리스트의 첫번째 가격, 마지막 가격을 기준으로 정렬이 예쁘게 잘 되는 걸 확인했다.

 

4. 코드의 중복 줄이기

작성하고 보니 똑같은 코드의 반복이 너무 많다. 줄여보기 도전!! 먼저 result라는 값이 모든 로직에서 동일하게 사용되기 때문에 맨 위로 빼줬다. 

class ProductListView(View):
    def get(self, request):
        
        sort_condition = request.GET.get('sort-by')   
        
        products = Product.objects.all()   
            
        result = [{ 'id'       : product.id, 
                    'name'     : product.name,
                    'image_url': product.image_url,
                    'prices'   : 
                        [int(p.price) for p in product.productoption_set.filter(product_id = product.id)]
                    } for product in products] 
        
        if sort_condition == 'price':
                    
            sorted_result = sorted(result, key = lambda x : x['prices'][0])
        
            return JsonResponse({'message':'Ordered by lowest price', 'result':sorted_result}, status=200)
        
        elif sort_condition == '-price':
        
            sorted_result = sorted(result, key = lambda x : x['prices'][0], reverse=True)
        
            return JsonResponse({'message':'Ordered by highest price', 'result':sorted_result}, status=200)
        
        elif sort_condition == '-id':
            
            products = products.order_by('-created_at')
            
            sorted_result = [{  'id'       : product.id, 
                                'name'     : product.name,
                                'image_url': product.image_url,
                                'prices'   : 
                                    [int(p.price) for p in product.productoption_set.filter(product_id = product.id)]
                                } for product in products] 
            
            return JsonResponse({'message':'Ordered by newest', 'result':sorted_result}, status=200)
        
        return JsonResponse({'result':result}, status=200)

하지만 최신순으로 정렬하는 경우는 Product에 있는 컬럼값을 기준으로 정렬한 뒤에 result를 생성해야하는데, 이 경우에는 해당 result값을 새로 만들어야했다.(이제까지 내 생각에는?) 그래서 고민끝에 created-at이 아닌, id값을 기준으로 정렬하기로 코드를 수정했다.

        elif sort_condition == '-id':
            
            # id도 생성한 순서대로 생기는 고유값이니까 이를 이용해서 역순으로 정렬하면 최신순이 된다!.
            sorted_result = sorted(result, key = lambda x : x['id'], reverse=True)
            
            return JsonResponse({'message':'Ordered by newest', 'result':sorted_result}, status=200)
        
        return JsonResponse({'result':result}, status=200)

하고나서 보니까 계속해서 반복되고 있던 return JsonResponse ~~~ 코드들. 이 코드들도 하나로 통일하여 줄여줬다.

class ProductListView(View):
    def get(self, request):
        
        sort_condition = request.GET.get('sort-by')   
        
        products = Product.objects.all()   
            
        result = [{ 'id'       : product.id, 
                    'name'     : product.name,
                    'image_url': product.image_url,
                    'prices'   : 
                        [int(p.price) for p in product.productoption_set.filter(product_id = product.id)]
                    } for product in products] 
        
        if sort_condition == 'price':
                    
            result = sorted(result, key = lambda x : x['prices'][0])
        
        elif sort_condition == '-price':
        
            result = sorted(result, key = lambda x : x['prices'][0], reverse=True)
    
        elif sort_condition == '-id':
            
            result = sorted(result, key = lambda x : x['id'], reverse=True)
        
        return JsonResponse({'result':result}, status=200)

각 정렬값에 따른 결과값에 대한 메세지를 주고 싶었지만, f스트링을 사용해야하나..? 도저히 모르겠어서 message를 지워버리고 result로 대체했다. 깔끔해진 코드 뿌듯하다!!! 이제 다음은... 사이즈 필터링이 남았다...

 

 

* 많은 도움이 됐던 블로그

https://velog.io/@suasue/Django-%EC%A0%95%EB%A0%AC-%EB%BD%80%EA%B0%9C%EA%B8%B0-order-by-annotate-extra

728x90