DotFriends

🚀 상품 리스트 조회 API분기 줄이기

King of Silicon Valley 2021. 9. 25. 16:53
728x90

상품리스트 조화 API의 분기를 줄이는 과정에 대한 포스팅이다. 

 

기존의 상품 리스트조회 API코드를 살펴보면 

class ProductsView(View):
    @input_validator
    def get(self, request):
        option   = request.GET.get('option', None)
        offset   = int(request.GET.get('offset', 0))
        limit    = int(request.GET.get('limit', 10))
        order    = request.GET.get('order', 'id')
        search   = request.GET.get('search', None)
        category = request.GET.get('category', 0)
        
        q = Q()
        if option == 'new' or category == 'new':
            q = Q(is_new=True)
            category_name = 'NEW'

        if option == 'sale' or category == 'sale':
            q = Q(~Q(discount_percent=0))
            category_name = 'SALE'

        if (category != 'new' and category != 'sale') and category:
            q &= Q(category_id=category)
            category_name = categories[category]

        if search:
            q &= Q(name__icontains = search)
            category_name = search

        products = Product.objects.filter(q).prefetch_related('image_set')\
            .annotate(avg_rate=Avg('comment__rate'),popular=Count("userproductlike", distinct=True),review_count=Count('comment',distinct=True))\
            .order_by(order)[offset:offset+limit]

		이하 생략

 

쿼리 파라미터에 들어온 값을 합치는 부분에서 

q = Q()
        if option == 'new' or category == 'new':
            q = Q(is_new=True)
            category_name = 'NEW'

        if option == 'sale' or category == 'sale':
            q = Q(~Q(discount_percent=0))
            category_name = 'SALE'

        if category:
            q &= Q(category_id=category)
            category_name = categories[category]

        if search:
            q &= Q(name__icontains = search)
            category_name = search

 

총4번의 분기가 생긴다. 

분기가 많다 -> 테스트코드의 양이 많아진다. , 디버깅이 어려워진다. 

이런 문제점을 인식하고 4개의 분기를 1개로 줄여보겠다. 

 

4개의 조건문들을 살펴보면 쿼리파라미터에 값이 있거나  원하는 값이여야 한다는 조건이다. 

 

그래서 

request.GET.items()

를 사용할 것이다. 

이렇게 하면 쿼리파라미터 키값과 값을 쌍으로 갖고 있는 이터레이블 객체가 나온다. 

 

이터레이블 객체를 풀어주기 위해 아래와 같이 한다. 

{i : v for i,v in request.GET.items()}

 

예를 들어서 쿼리 파라미터 order에 id category에 3을 넣고 위 코드를 실행하면 

 

{'order' : id, category : 3}

과 같은 형태의 딕셔너리가 만들어진다. 

그렇다 이렇게 하고 쿼리 파라미터를 안주면 딕셔너리에 안 담기게 되고 쿼리 파라미터에 값이 있는지 없는지를 검사하지 않아도 된다. 

 

그리고 이렇게 filter_set이라는 딕셔너리를 만들어 두고 

filter_set = {
                "search"   : "name__icontains",
                "new"      : "is_new",
                "sale"     : "discount_percent__gte",
                "category" : "category_id"
            }
q = {filter_set.get(i):v for (i,v) in request.GET.items() if filter_set.get(i)}

위와 같이 딕셔너리를 생성하고

쿼리 파라미터 sale에 10  category에 3을 넣으면 

 

{ "discount_percent__gte" : 10 , "category_id" : 3 }

이런 형태의 딕셔너리가 생성된다. 

 

쿼리파라미터 값이 없으면 딕셔너리에 담기지 않고 Q객체를 통해서 쿼리셋을 합치지 않고 한번에 쿼리셋을 딕셔너리 형테로 만들 수 있다. 

 

생성된 딕셔너리를 

 

Product.objets.filter(**q)

 

언팩킹 해서 집어 넣으면 

 

Product.objets.filter("discount_percent__gte"=10 , "category_id"=3)

이런 형태가 된다. 

 

최종 코드를 보자면 

 

filter_set = {
                "search"   : "name__icontains",
                "new"      : "is_new",
                "sale"     : "discount_percent__gte",
                "category" : "category_id"
            }               

q = {filter_set.get(i):v for (i,v) in request.GET.items() if filter_set.get(i)}
                            
products = Product.objects.filter(**q).prefetch_related('image_set')\
                .annotate(avg_rate=Avg('comment__rate'),popular=Count("userproductlike", distinct=True),review_count=Count('comment',distinct=True))\
                .order_by(order)[offset:offset+limit]

 

if문은 딕셔너리를 생성할 때 쓰이는 부분 1개만 남게 된다. 

if filter_set.get(i)

파이써닉한 코드로  분기를 없앨 수 있었다.

 

근데 이렇게 했을 때 쿼리 파라미터에 이상한 값을 넣은 경우는 어떻게 처리했는지에 대한 과정은 https://kingofsiliconvalley.tistory.com/26?category=1046479이 포스팅을 참고하자