[Django] Q를 활용한 다중 필터링

Wonbin Lee·2022년 4월 3일
2

Django

목록 보기
6/6

다중 필터링 기능

내가 진행하는 프로젝트에서 상품에대한 카테고리는 Main_category와 그안에 속해있는 Category 로 나뉘어 있다.
나는 각 상품에 필터링을 하여, 내가 원하는 카테고리에 해당하는 상품들을 프론트에 전달하는 기능을 구현하고 싶었다. 하지만 Qurey Parameter도 처음 써보고(getlist가 있는 줄도 몰랐다..), 다중 필터 구현기능도 처음이라 생각처럼 기능 구현이 쉽지 않았다.

처음 시도한 다중필터링 코드

import json

from django.http import JsonResponse
from django.views import View
from django.core.paginator import Paginator, EmptyPage

from utilities.decorators import check_token
from .models import *

class ProductsView(View):
    def get(self, request):
        # :8000/products?main=boy&sub=tops,pants,acc
        main = request.GET.get('main')
        subs = request.GET.get('sub')

        if subs == None:
            main_category = MainCategory.objects.get(title = main)
            categories    = Category.objects.filter(main_category = main_category)
            product_list  = []

            for category in categories:
                products = Product.objects.filter(category = category)
                product_list += [{
                    'id'           : product.id,
                    'name'         : product.name,
                    'price'        : product.price,
                    'images'       : [image.image_url for image in product.images.all()],
                    'detail'       : product.detail,
                    'character'    : product.character.name,
                    'product_sizes': [{
                        product_size.size.size_tag : product_size.stock
                    } for product_size in ProductSize.objects.filter(product = product)]
                } for product in products]

            return JsonResponse({'result' : product_list, "count" : len(product_list) }, status = 200)
        
        elif subs != 'none_sub':
            sub_lst      = subs.split(',')
            sub_products = []
            
            for sub in sub_lst:
                sub_categories = Category.objects.filter(title = sub)
                for sub_category in sub_categories:
                    if sub_category in MainCategory.objects.get(title = main).categories.all():
                        sub_products += [{
                            'id'           : sub_product.id,
                            'name'         : sub_product.name,
                            'price'        : sub_product.price,
                            'images'       : [image.image_url for image in sub_product.images.all()],
                            'detail'       : sub_product.detail,
                            'character'    : sub_product.character.name,
                            'product_sizes': [{
                                product_size.size.size_tag : product_size.stock
                            } for product_size in ProductSize.objects.filter(product = sub_product)]
                        } for sub_product in Product.objects.filter(category = sub_category)]
                    
                    else:
                        return JsonResponse({'message' : 'IVALID_URL'}, status = 400)
                
            return JsonResponse({'result' : sub_products, "count" : len(sub_products) }, status = 200)

내가 처음 구현한 다중 필터링 기능이다. 우선 장고 Q 객체의 존재도 몰랐고, 내가 아는 지식 선에서 필터기능을 구현하려고 했기에 시간이 굉장히 걸렸었다.
기능은 정말로 완벽하게 작동된다. 하지만 문제가 너무 많다는 것을 나는 알 수 있었다.

우선 첫번째 문제는 전달받은 Qurey Parameter를 ',' 를 기준으로 하여 split을 활용하여 나누기 때문에 RESTful 하지 못하다는 점이 있다.

두번째 문제는 코드의 가독성이 매우 떨어진다는 점이다. 데이터 모델을 가져와서 for 문을 이용한 반복루프를 너무 많이 사용하고, 그로 인하여 코드가 매우 길어진다.
또한 지정해줘야 할 변수또한 너무 많아서 코드의 가독성이 매우 좋지않다.

마지막으로 두번째 문제에서도 나왔던 for문을 이용한 반복루프와, 길어진코드로 인하여 레이턴시가 너무 길어진다는 문제가 있다.

이러한 문제를 인지하고 해결책을 찾기위해 구글링을 하던 중에 장고에서 제공하는 Q객체에 대해 알게 되었다.



Q 객체

일반적으로 filter() 메소드를 사용하면 and 조건만 사용 가능하다.
장고 ORM에서 MySQL 쿼리문 처럼 or 조건을 쓰고 싶다면 Q객체를 사용하면 된다.

sql 쿼리문

SELECT * FROM products WHERE main_category=boy OR category=boy-top

장고 orm

Product.objects.filter(Q(main_category=boy) | Q(category=boy-top))

Q를 활용한 다중 필터링

Q를 활용하여 다시 작성한 코드.

import json

from django.http import JsonResponse
from django.views import View
from django.db.models import Q
from django.core.paginator import Paginator, EmptyPage

from utilities.decorators import check_token
from .models import *

class MainProductView(View):
    def get(self, request):
    #http://127.0.0.1:8000/products?main=girl&sub=girl-bottom&sub=girl-ACC
        try:
            main_category = request.GET.get('main', None)
            sorting       = request.GET.get('order-by', 'latest')
            page          = request.GET.get('page', 1)
            category      = request.GET.getlist('sub', None)
            character     = request.GET.getlist('character', None)
            
            sorting_dict = {
                'low-price' : 'price',
                'high-price': '-price',
                'latest'    : '-pk'
            }

            q = Q()
            if main_category:
                q &= Q(category__main_category__title=main_category)
            if category:
                q &= Q(category__title__in=category)
            if character:
                q &= Q(character__name__in=character)
    
            products     = Product.objects.filter(q).order_by(sorting_dict[sorting])
            total_count  = len(products)

            paginator    = Paginator(products, 5)
            page_obj     = paginator.page(page)

            product_list = [{
                'id'           : product.id,
                'name'         : product.name,
                'price'        : product.price,
                'images'       : [image.image_url for image in product.images.all()],
                'detail'       : product.detail,
                'character'    : product.character.name,
                'product_sizes': [{
                    product_size.size.size_tag : product_size.stock
                } for product_size in ProductSize.objects.filter(product = product)]
            } for product in page_obj]      
           
            return JsonResponse({'result' : product_list, 'count' : total_count}, status=200)
        
        except Product.DoesNotExist:
            return JsonResponse({'message' : 'PRODUCT_NOT_EXIST'}, status=400)
        except EmptyPage:
            return JsonResponse({'message' : 'PAGE_NOT_EXIST'}, status=400)

(코드내에 paginator 부분은 팀원들과 시도하고 있는 기능이기 때문에 신경쓰지 않아도 된다.)

Q 객체를 활용하여 필터링을 적용하니 훨씬 간결해진 모습이다.

Q()는 Products.objects.all() 과 같은 의미이다.
연속되는 if문을 통해 q라는 변수에 조건식을 계속 추가 적용시키는 개념으로 이해하면 된다.
그리고 마지막에 q조건을 넣은 product 객체를 생성하는 것이다.

Q()속 조건식의 내용은 JOIN임을 기억하자.

profile
Developer who level up every day ✌️

0개의 댓글