Django | 필터링 뽀개기! if문, 딕셔너리, Q 객체 활용

Sua·2021년 2월 28일
9

Django

목록 보기
20/23
post-thumbnail

장고에서 필터링 기능을 구현할 수 있는 방법에는 크게 3가지 방법이 있다.
if문을 이용하는 방법, 딕셔너리를 이용하는 방법, Q 객체를 이용하는 방법이다.

오늘의 집을 예시로 해서 3가지 방법을 각각 구현해보도록 하겠다.

오늘의 집에서는 다양한 조건을 한 번에 적용할 수 있으며 하나의 기준에도 여러 개의 조건을 선택을 할 수 있다. 예를 들어 색상을 고를 때 여러 가지 색을 선택할 수 있으며, 동시에 사용인원도 선택할 수 있다.

여기서는 color와 size 두 가지 기준으로 필터링 기능을 구현해보록 하겠다.

카테고리별 조회도 필터링 기능이라 할 수 있다. 해당 카테고리 조건에 맞는 상품들만 보여주는 것이기 때문이다.

오늘의집 카테고리는 원래 3~4 depths로 되어있는데 여기서는 카테고리를 3단계로만 나누어 최상위 카테고리부터 category, sub_category, detail_category로 구분할 것이다.

if문을 활용한 필터링

class ProductView(View):
    def get(self, request):
        category        = request.GET.get('category', None)
        sub_category    = request.GET.get('subcategory', None)
        detail_category = request.GET.get('detailcategory', None)
        color           = request.GET.getlist('color', None)
        size            = request.GET.getlist('size', None)
        
        if category:
            products = Product.objects.filter(detail_category__sub_category__category=category)

        if sub_category:
            products = products.filter(detail_category__sub_category=sub_category)

        if detail_category:
            products = products.filter(detail_category=detail_category)

        if color:
            products = products.filter(productoption__color__in=color).distinct()

        if size:
            products = products.filter(productoption__size__in=size).distinct()

카테고리는 하나만 선택할 수 있므로 request.GET.get을 사용했고, color와 size는 다중선택이 가능하므로 request.GET.getlist를 사용했다. color와 size에는 list 형태로 값이 들어 있다. 따라서 filter를 적용할 때도 __in을 같이 써줘야 한다. filter(productoption__color__in=color) 이렇게 말이다.

__in 사용예시

id가 1,2,3인 데이터를 모두 찾으려면
Product.objects.filter(id__in=[1,2,3]) __in과 list를 활용하면 된다.

if문으로 작성하면 새로운 조건이 생겨날 때마다 if문이 계속 늘어난다.
if문이 많다고해서 늘 비효율적인 코드는 아니지만,같은 구조가 반복되고 있다.

if (variable):
    products = products.filter(column_name = condition).distinct()

이렇게 optional한 조건이 반복되는 경우가 백엔드에서 필터링 할 때에는 굉장히 자주 나타나는 일이다. 파이썬에서는 이렇게 optional한 multi-variable을 기준으로 필터링을 할 때에 if 문을 나열하기 보다는 key-value 의 형태를 활용하는 것이 보다 효율적이라고 할 수 있다.

즉, 조건들을 딕셔너리 자료형으로 만들어서 활용하면 확장성이 늘어나고 코드는 간결해질 수 있다.

딕셔너리를 활용한 필터링

class ProductView(View):
    def get(self, request):
        filter_categories = {
            'category'       : 'detail_category__sub_category__category__in',
            'subcategory'    : 'detail_category__sub_category__in',
            'detailcategory' : 'detail_category__in',
            'color'          : 'productoption__color__in',
            'size'           : 'productoption__size__in'
        }

        filter_set = {
            filter_categories.get(key) : value for (key, value) in dict(request.GET).items() if filter_categories.get(key)
        }
        
        products = Product.objects.filter(**filter_set).distinct() 

이 방식의 단점은 하나만 들어와도 list 안에 담긴다는 것이다. __in을 꼭 써줘야 한다는 뜻이다.

이 말이 무슨 뜻이 이해하려면 print(request.GET)을 찍어보면 된다.

# Httpie 요청
http -v GET 127.0.0.1:8000/products category==1 color==2 size==3
# 출력 
<QueryDict: {'category': ['1'], 'color': ['2'], 'size': ['3']}> # print(request.GET)

request.GET이 QueryDict형태이고, 'category': ['1']과 같이 category에 값이 하나만 들어와도 리스트로 담기는 것을 볼 수 있다.

다른 예시를 들어보자. color에 1, 2, 3 세 가지 값이 들어온 상황이다.

# Httpie 요청
http -v GET 127.0.0.1:8000/products color==1 color==2 color==3
# 출력 
<QueryDict: {'color': ['1', '2', '3']}> # print(request.GET)

현재 key는 'color'이고 value는 ['1', '2', '3']이다.

그리고 filter_set = {'productoption__color__in' : ['1', '2', '3']}이 된다.

여기서 딕셔너리 언패킹의 개념이 등장한다.
딕셔너리 언패킹의 개념은 파이썬 코딩 도장에 잘 설명되어 있다.

간단하게 설명하면 딕셔너리인 x 앞에 별표 두개 **를 붙이면 언패킹이 되어 키워드 인수로 사용할 수 있다는 것이다.

다시 우리의 예시로 돌아오면

Product.objects.filter(**filter_set)가 딕셔너리 언패킹에 의해
Product.objects.filter(productoption__color__in=['1', '2', '3']) 이렇게 변한다.

Q 객체를 활용한 필터링

from django.db.models import Q

class ProductView(View):
    def get(self, request):
        category        = request.GET.get('category', None)
        sub_category    = request.GET.get('subcategory', None)
        detail_category = request.GET.get('detailcategory', None)
        color           = request.GET.getlist('color', None)
        size            = request.GET.getlist('size', None)

        product_condition = Q()

        if category:
            product_condition.add(Q(detail_category__sub_category__category=category), Q.AND)

        if sub_category:
            product_condition.add(Q(detail_category__sub_category=sub_category), Q.AND)

        if detail_category:
            product_condition.add(Q(detail_category=detail_category), Q.AND)

        if color:
            product_condition.add(Q(productoption__color__in=color), Q.AND)
            products = products.filter(productoption__color__in=color).distinct()

        if size:
            product_condition.add(Q(productoption__size__in=size), Q.AND)

        products = Product.objects.filter(condition).distinct()

Q객체와 if문을 활용해 필터링을 할 수 있다.

참고사이트
https://velog.io/@devmin/wecode-2nd-project-DaNaeBang
https://velog.io/@wltjs10645/Sorting-Filtering-%ED%95%98%EA%B8%B0
https://intellipaat.com/community/33243/how-can-i-filter-a-django-query-with-a-list-of-values
https://brownbears.tistory.com/425?category=168282

profile
Leave your comfort zone

0개의 댓글