이번 시간에는 django의 Q object로 복잡한 filter를 조금 깔끔하게 작성하는 방법을 알아보겠습니다. 물론, 문서에 예제가 매우 잘 나와 있긴 합니다.

 


 먼저, 데이터는 요래 있습니다. 작성자가 "alpha"인 사람은 "beta"라는 내용의 포스트를 썼고, 작성자가 "admin"인 유저는 "alpha" 라는 내용의 포스트를 썼습니다. 여기까지 보면 별로 어려울 게 없어 보입니다. 자. 그런데, 어떠한 정보를 얻어올 때, 쿼리 파라미터가 여러개 들어오는 것은 보통입니다. 예를 들어 네이버에 "코딩테스트" 를 검색하고 view 탭만 봐도 query parameter가 상당히 많음을 볼 수 있어요.

 

 이런 것을 어떻게 깔끔하게 처리할까요? 문제 상황이 이해가 가셨으리라 생각됩니다.

 

 

 저는 post의 유저별로도 필터링을 걸고 싶고, keyword별로도 필터링을 걸고 싶습니다. 물론, query parameter를 받아서요. 여기서, keyword는 포스트에 해당 내용이 있는지를 판별합니다. 사실, 이보다 더 현실적인 것은 글의 type이 있고 keyword 정도가 있겠죠? 혹은 인플루언서 여부와 keyword 정도가 더 있을까요? 자. 그런데, user와 keyword 파라미터가 있는 경우와 없는 경우 모두 고려하면 총 4가지입니다.

 

 이 경우에 대해, 모든 if else문을 돌려가면서 queryset을 생성한다고 하면 아래와 같이 될 겁니다.

 

 

 의도한 대로 동작하는지 확인해 봅시다.

 

 

 먼저, a라는 내용을 포함하는 포스트는 2개가 나오는 게 맞습니다.

 

 

 다음에 유저가 alpha이면서, b라는 내용을 포함하는 포스트는 1개밖에 없습니다. 제대로 동작하는 것은 맞습니다. 그런데, if else문의 향연이라. 생각보다 끔찍합니다.

 

 위와 같이 코딩하면, 코드 길이를 많이 줄일 수 있습니다. user_name이 있으면 filter를 걸고, keyword가 있으면 또 filter를 걸어버립니다.

 

 결과는 제대로 나옵니다. 문제는, filter를 계속 걸면 and의 효과가 나게 됩니다. or는 어떻게 적용해야 하나요? not A and not B로 적용해야 하나요? 저는 단지, where 절의 역할을 하는 filter 부분을 생성하는 부분을 아래와 같이 "분리"하고 싶습니다.

 

 책임을 분리시킨 셈입니다. 기존에는 그냥 쿼리를 통으로 처리했다면, where filter를 만드는 책임을 다른 부분에서 처리해서, 보다 깔끔하게 만듭니다. 그러면서 보다 복잡한, 예를 들면 OR 쿼리라던지 not 쿼리 같은 complex filter도 처리하고 싶습니다.

 


 이럴 때 쓸 수 있는 것이 Q 객체입니다.

 

 Q 객체는 filters가 캡슐화가 되어 있는데요. &이나 | 연산과 합쳐질 수 있습니다.

 

 

 그러면 이런 걸 어떤 것과 써 먹는가? add 함수를 이용해서 쿼리를 연결하면, 꽤 복잡한 쿼리를 만들어낼 수 있습니다. data와 기존 정보를 어떤 것으로 연결할지에 대한 정보를 add 메서드에 주면, 알아서 연결해 주게 됩니다.

 

 위 코드를 보겠습니다. 19번째 줄에, user_name이 query parameter에 있으면, user의 이름이 user_name인지를 filtering 하는 Q를 추가합니다. 먼, 두 번째에 q.AND가 왔으니, 연결 타입이 AND가 됩니다. 다음에, keyword가 있는 경우, post에 keyword가 포함되어 있는 filtering 하는 Q를 추가합니다. 이를 그림으로 그려 보면 아래와 같습니다. user와 keyword가 모두 쿼리 파라미터로 들어왔다고 합시다.

 

 

 19번째 줄에서 현재 q인 하늘색 부분과 username filter를 연결해요. AND connector로요.

 

 

 21번째 줄에서, 현재 q인 tree인 하늘색 부분과 post contain filter와 연결합니다.

 

 

 키워드 파라미터와 유저 파라미터 두 개를 주었습니다.

 

 

 그렇기 떄문에, auth_user.username이 "alpha"인 것과, myapp_post.post에 "b"가 포함되어 있는 조건 2개가 and로 연결된 조건절이 생성되었음을 알 수 있어요.

 

 이건 어떨까요? 단순하게 keyword 파라미터만 줬습니다. 이 때에는 그냥, post에 "c"가 포함되어 있는지 필터링 하는 where 조건절이 추가될 겁니다.

 

 

 보시면 myapp_post.post LIKE %c%만 조건절에 있음을 확인할 수 있습니다.

 

 


 이 Q 객체의 강력한 점은 OR 쿼리도 생각보다 손쉽게 연결할 수 있다는 점입니다.

 

 먼저, user_name이 있는 경우, username 필터를 기존 정보와 AND로 연결합니다. 다음에, 21번째 줄에, keyword 조건을 기존 정보와 OR로 연결하게 되는데요. 이렇게 되면, username 파라미터와 keyword 파라미터가 같이 들어왔을 때 아래와 같은 쿼리를 수행하게 됩니다. 21번째 줄을 그림으로 표현하면 아래와 같습니다.

 

 

 connect type이 AND가 아닌 OR이였기 때문에, 위 그림과 같이 연결됩니다.

 

 

 두 번쨰 조건인 post에 특정 내용이 있다는 조건이 1번째 조건과 OR로 연결되었음을 보시면 됩니다.