[Django] filtering 구현 방법 : dictionary와 Q()

채록·2021년 5월 2일
2

Python & Django

목록 보기
34/34
post-thumbnail

filtering을 구현하기 위해 선택한 것은 선택지에 대한 값을 query parameter로 받는것이었다. 실제로 웹 서핑을 하면서 필터링에 따른 데이터를 보면 URI의 형태가 다음과 같음을 확인할 수 있다.

http://127.0.0.1:8000/posting?category=1&housing=1

위 주소에서 port 번호 (8000)앞은 무시하고 그 뒤를 살펴보면 다음과 같다.

  • posting : posting으로 지정된 경로의 데이터
  • ? : query parameter에 대한 값이 시작될때 붙는 기호이다
  • & : 여러개의 값이 있을때 각 값을 연결시키는 기호이다
  • category=1 category가 1인 값을 지정
  • housing=1 housing이라는 값이 1인 것을 지정

그럼 이와 같은 값이 server에게 request로 보내졌을때 어떻게 처리되는가?



I. path parameter

어떠한 목록이 있는데 그중 단 한가지만 선택했을때는 path parameter로 로직을 구현하였다.
예를들면 다음과 같다.

  • 전체 상품 목록 : /products
  • 그 중에서 상품 1번인 것에 대한 상세 페이지 접근 : /products/1

그러면 server에서의 로직은 다음과 같다.

# Django_views.py
def ProductDetailView(self, request, product_id)
  product = Product.objects.get(id=product_id)

# Django_urls.py
path('/products/<int: product_id>', ProductDetailView.as_view())

products 뒤에 들어온 값에대해 product_id라는 변수이름을 갖게 해준 뒤 SQL문에서 조건으로 잡아 해당 조건에 대한 값만 가져오는데 사용한다.
만약 product_id 에 대한 parameter가 입력되지 않으면 해당 경로로 접근하지 못한다.


II. query paramter

이와 달리 query paramter는 URI 경로상의 지정이 아니라 request.GET으로 가져오는 값이다. request.GET은 GET으로 받는 인자들을 다 포함하는 딕셔너리 객체 로 위의 예시를 가져오면 다음과 같다.

request.GET : <QueryDict: {'order': ['best'], 'housing': ['1', '2']}>
[02/May/2021 13:06:38] "GET /posting?order=best&housing=1&housing=2 HTTP/1.1" 200 22481

dict 안에 list ?
형태를 보면 dictionary 형태는 맞되, value값의 형태가 list 이다. 때문에 같은 key값으로 여러개의 값이 들어오면 list안에 모두 담기는 형태이다.
이 형태를 사용해 다중선택 filtering구현도 가능하다. 물론 이렇게 보내줄 수 있도록 프론트분과 얘기를 잘 해야겠지만!

이때 request.GET으로 해당 key의 value를 바로 가져오면 list형태가 아니라 값 형태로 출력된다.

request.GET['order'] : best

- request.GET.getlist()

위의 방법대로 가져왔을 경우 2개의 값이 들어있는 housing의 출력형태가 의도와 달라진다.

request.GET['housing'] : 2

1과 2를 입력했는데 나중에 입력된 2만이 출력되는 것.
list형태 그대로를 받기 위해선 getlist()를 사용해 주어야 한다.

getlist() 메소드를 통해 들어온 query paramter의 값을 list로 받을 수 있다. 따라서 중복되는 key의 여러값들을 모두 처리할 수 있다.

request.GET.getlist('housing') : ['1', '2']

- request.GET.get()

get() 메소드는 확인하고자 하는 key값이 dictionary에 존재하면 그 key의 value값을 가져오고, 존재하지 않으면 default값으로는 None을 혹은 사용자 지정 값을 가져오게 할 수 있다.

정렬조건을 구현할때 query parameter로 유저가 지정한 조건을 받아도 되지만, 그 값이 없을 경우 기본적으로 "최신순"으로 보여주는 사이트가 많다. 나 또한 프로젝트를 진행할때 이와 같이 구현하기 위해 다음과 같이 코드를 작성했다.

order_request = request.GET.get('order', 'recent')


III. dictionary로 filtering 처리

request.GET으로 GET method를 통해 들어온 값을 dictionary로 받는다고 하였다. 그럼 그 구조를 이용해 보자!

1) 정렬 (order)

order라는 key값에 어떤 value가 오느냐에 따라 해당 value와 같은 이름으로 지정된 key값이 order_prefixes라는 선언된 dictionary에서 찾아 해당 key값의 value대로 정렬하도록 구현했다.

2) filtering

또한 request.GET으로 어떤 filtering 조건이 들어올지 모르는 상태에서 일단 내가 허용하는 filtering 조건에 대한 dictionary를 작성했다.

__in ?
이 뜻은 들어온 값에 대한것이 포함되어 있는, 즉 OR 조건을 허용하게 하는 것이다.

차례대로 설명해보자

  • for (key, value) in dict(request.GET).items() : request.GET으로 들어온 dictionary의 key-value를 모두 검사한다. 그리고 key에는 key, value에는 value라는 변수를 지정하여 반복문을 진행한다.
  • filter_prefixes.get(key) : value : 들어온 key값에 대한 value를 이미 만들어 놓았던 dictionary (filter_prefixes)에서 값을 찾는다. 그 value를 새로 key로 지정하고, 반복문을 진행하는 value값을 value로 지정해 새로운 형태의 dictionary를 만든다.
  • if filter_prefixes.get(key) : 위 로직이 구현되는 조건문이다. 즉, 반복문을 진행하는 key의 값과 같은 값을 가지는 key가 filter_prefixes에 있고, 그에 따른 value가 있을 경우에만 진행하도록 한다.

예시

?order=best&housing=1&housing=2&space=1 이라는 조건으로 query parameter에 담아 request를 보냈을때 filter_set이라는 새로운 dictionary가 어떻게 작성되는지 확인하면 다음과 같다.

{'housing_id__in': ['1', '2'], 'space_id__in': ['1']}


3) 최종 조건으로 객체 불러오기

정렬과 filtering 조건에 따라 객체 불러오는걸 한번에 처리하였다.

postings = postings.filter(**filter_set).order_by(order_prefixes[order_request])

이때 사용한것이 keyword argument이다 (**kwargs). 몇개가 들어올지 모르는 상황이기 때문!





IV. Q()로 filtering 구현하기.

참고자료 : https://brownbears.tistory.com/425

django에서 제공하는 Q()
마치 SQL문에서 WHERE 구문에 조건을 추가하는 느낌이 팍팍드는 방법이다.

예시로는 상품 상세페이지 내에서 리뷰를 filtering 할때를 들어보자. 상세페이지로 들어갈 상품의 id는 path paramter로 받고, filtering할 리뷰의 별점은 query paramter로 받는다.

좀더 간편하게 사용하기 위해 변수 q가 Q()메소드를 상속받아 일명 q 객체가 된다.

q = Q()

이렇게 되면 q.으로 간편하게 사용할 수 있다.

q = Q()
row = request.GET.get('category')
q.add(Q(category_id=row))

OR와 AND 조건 추가

q = Q()

#input : http://127.0.0.1:8000/products/13/review?rate=5&rate=3
product = Product.objects.get(id=product_id)
row_list = request.GET.get('rate') # print(rate) => ['5', '3']
for row in row_list:
	q.add(Q(rate=row), q.OR)
q.add(Q(product=product), q.AND)

위 코드로 출력되는 q 는 (AND: (OR: (AND: ), (AND: ('rate', '5')), ('rate', '3')), (AND: ('product', <Product: Product object (13)>))) 이다.

  • (AND: (OR: (AND: ) : 전체적인 로직의 구조를 뜻한다.가장 크게 AND로 묶여있고, 그 안은 OR, 가장 작은 쪽은 AND 임을 뜻한다.
  • (AND: ('rate', '5')), ('rate', '3') : 별점조건이 5이거나 3인것을 뜻한다. OR 이다.
  • (AND: ('product', <Product: Product object (13)>)) : AND조건으로 product를 특정짓는다.

적용해서 객체 가져오기

이제 이 query set 으로 반복문을 진행해 원해는 key이름에 정보를 담으면 끝!





끝!

dictionary와 Q()를 사용해 filtering을 구현하는 방법을 모두 진행해보았다. 만일 들어오는 조건의 key이름이 제한적이라면 Q()를 사용해도 괜찮아보이지만 그게 아니라면 오히려 dictionary가 한눈에 코드이해하기에도 편하지 않나 싶다.

profile
🍎 🍊 🍋 🍏 🍇

1개의 댓글

comment-user-thumbnail
2021년 5월 3일

❤️

답글 달기