장고에서 필터링 기능을 구현할 수 있는 방법에는 크게 3가지 방법이 있다.
if문을 이용하는 방법, 딕셔너리를 이용하는 방법, Q 객체를 이용하는 방법이다.
오늘의 집을 예시로 해서 3가지 방법을 각각 구현해보도록 하겠다.
오늘의 집에서는 다양한 조건을 한 번에 적용할 수 있으며 하나의 기준에도 여러 개의 조건을 선택을 할 수 있다. 예를 들어 색상을 고를 때 여러 가지 색을 선택할 수 있으며, 동시에 사용인원도 선택할 수 있다.
여기서는 color와 size 두 가지 기준으로 필터링 기능을 구현해보록 하겠다.
카테고리별 조회도 필터링 기능이라 할 수 있다. 해당 카테고리 조건에 맞는 상품들만 보여주는 것이기 때문이다.
오늘의집 카테고리는 원래 3~4 depths로 되어있는데 여기서는 카테고리를 3단계로만 나누어 최상위 카테고리부터 category, sub_category, detail_category로 구분할 것이다.
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'])
이렇게 변한다.
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