슈코딩
[Django] Q() 객체 Query 활용 본문
오늘은 추천시스템 프로젝트에서도 검색기능을 만들때 썼었고, 이번 DRF과제에서도 사용을 한 Q 에 대해서 정리를 해보려고 한다. 우선 Q는 간단하게 쿼리 그자체를 객체로 다룰수 있게 하는 class이다. Django 에서 사용하는 filter(), get()같은
ORM안의 옵션을 Q()를 사용하여 동일하게 적용 시킬 수 있다. 특징으로는 and, or 를 적용 시킬수 있다는 점이다.
글로만 설명하면 와닿질 않으니 예제를 보면서 알아봐야겠다.
from django.db.models.query_utils import Q
from rest_framework.views import APIView
class UserView(APIView):
def get(self, request):
#취미 중 산책이 있거나, 나이가 19살보다 많고, 김씨인 사람만 필터
query = Q(hobby__name="산책") | Q(age__gt=19, user__name__startwith="김")
user_profile_list = UserProfileModel.objects.filter(query)
#Q를 사용하지 않았을때
user_profile_list = UserProfileModel.objects.filter(hobby__name = "산책", age__gt=19, user__name__startwith="김")
위 예제를 보면 | 파이프가 들어가있는데, Q 객체에서는 연산자(&, |) 를 사용하여 and, or를 적용시킨다. 즉 산책이 취미에 포함되있거나 or 나이가 19살보다 많고 김씨인 사람만 필터 이런식으로 쿼리를 할 수 있게 된다. 반대로 Q를 사용하지 않으면 아래에 있는 예제처럼 전부가 다 포함된 쿼리가 된다. 이제 Q 객체가 무엇인지 알았으니 과제에서 활용한 다른예제도 다시 한번 봐보자.
class ProductView(APIView):
#DONE 상품 정보 가져오기
def get(self, request):
user= request.user
products = ProductModel.objects.filter(
Q(exposure_end__gte=timezone.now(), is_active=True) | Q(user=user)
)
#노출일자가 지나지않았고 or 로그인한 유저의 상품 을 가져오는 쿼리
class ArticleView(APIView):
#DONE 게시글 불러오기
def get(self, request):
articles = ArticleModel.objects.filter(
Q(end_article__gte=timezone.now(),
show_article__lte=timezone.now())).order_by("-show_article")
#노출종료일자와 노출시작일자 사이에있는 게시글만 최신순으로 가져오는 쿼리
#아침퀴즈풀이 Q사용 예제 및 'AND' 응용불가능 문제
문제. JobPostSerializer를 사용해서 고정적으로 두개의 기술스택 예시( django | mysql )을 기술스택으로 요구하는 회사를 검색 API 구현
6월 24일 아침퀴즈에서 나왔단 마지막문제는 Q쿼리를 사용하는 검색기능 구현이었는데, 이것을 하드코딩 하지 않고
for문을 돌려서 하는 방법을 풀이를 통해 알게되었다. 우선 for문을 안돌리고 처음에 배운내용대로면 아래 와같은 코드가
작성이 되는데, 기술(skill)이 하드코딩되어 있어서 실질적으로 다양한 기술에 대응하는 검색하는 기능이 이루어지지 않는다.
job_skills = JobPostSkillSet.objects.filter(
Q(skill_set__name='python') | Q(skill_set__name='mysql')
)
그래서 풀이를 보니 skills에 프론트에서 받은 파라미터 데이터를 리스트로 받아서 for문을 돌리고 query.add()를 활용하여
이문제를 해결 할수가 있었다. 특징적인것은 or를 사용할경우 뒤에 , Q.OR가 붙는다.
class SkillView(APIView):
def get(self, request):
skills = request.query_params.getlist('skills', '')
query = Q()
for skill in skills:
query.add(Q(skill_set__name=skill), Q.OR)
job_posts = JobPost.objects.filter(query)
print(query)
#(OR: (AND: ), (AND: ('skill_set__name', 'django')), ('skill_set__name', 'mysql'))
print(job_posts)
#<QuerySet [<JobPost: JobPost object (2)>, <JobPost: JobPost object (2)>,
#<JobPost: JobPost object (3)>, <JobPost: JobPost object (4)>,
#<JobPost: JobPost object (5)>, <JobPost: JobPost object (17)>,
#<JobPost: JobPost object (17)>]>
뒤에 Q.OR가 붙으면 or가 되니 나는 Q.AND하면 당연히 and가 될줄알고 한번 둘다 포함한 JobPost를 가져오려고 해봤다.
하지만 결과는 예상과는 달리 아무것도 반환이 되지않고 빈쿼리셋이 돌아왔다. 두개만 포함한 글이 없는건가 싶어서 DB도 확인 해봤지만, 1개가 DB에 있었다. 그러면 도대체 왜 안되는걸까..
이렇게 분명 query도 있고 데이터도 잘들어왔는데, 반환되는건 빈쿼리셋 뿐이었다. 구글링을 해봐도 Q.AND로 쓰이는 사례가 보였고, 현재 코드에 어느 부분에 문제가 있는건지 원인을 찾기가 어려워서 튜터님을 찾아가 봤다.
그러나 튜터님도 현재코드에서 AND가 왜 안되는지에 대한 명확한 답변은 못하셨고 결국에 우회해서 AND를 활용하는 법을 알려주셨다. 바로 부정의 부정은 긍정이라는 것이다.
#부정의 부정은 긍정.
query.add(~Q(skillset__name=skill), Q.OR)
job_posts = JobPost.objects.exclude(query)
이렇게 Q 앞에 ~ 를 붙이면 쿼리내용을 제외한이라는 뜻이된다. 즉 제외 한것을 제외하면 AND한것과 같은 결과물을 가져올 수 있다. ~라는 새로운 활용법을 알게되었지만 위예제는 하나의 참고용 활용법 일뿐 우회하는것이기 때문에 데이터가
많아지면 좋지 않은 방법일것이라고 생각이 들었다.
몇시간동안 붙잡고 AND를 적용시켜보려고 했지만 결국에 실패해서 허탈했다. 다음에 다른 예제를 만났을때 다시 한번
적용을 시켜보거나 쿼리 이해도가 더 높아지면 SQL문을 분석해서 방법을 찾아보고 싶었다. 아쉬움은 남았지만, ~ 라는
제외 방법을 알고가서 기분이 나쁘지많은 않았다.
'코딩공부 > Django' 카테고리의 다른 글
[Django] DRF 이용한 JWT사용 (0) | 2022.07.14 |
---|---|
[Django] DRF의 꽃 Serializer (0) | 2022.06.24 |
[Django] Admin 페이지 커스텀 (0) | 2022.06.24 |
[Django] DRF 특강 2,3일차 기본 개념 정리 (0) | 2022.06.20 |
[Django] DRF 특강 1일차 (0) | 2022.06.15 |