3월 06, 2022

[Django] Q객체

django 공식 문서에 기반하여 게시물을 작성했습니다.

Q객체는 데이터베이스 관련 작업에서 사용할 수 있는 SQL 조건을 나타냅니다. 

Q객체는 |(OR) , &(AND)등을 활용하여 조건들을 정의하고, 재사용하고, 합칠 수 있게 합니다. 


Keyworded argument queries는 filter() 등에서 AND조건으로만 합쳐집니다.

복잡한 쿼리를 실행하기 위해서(OR 등을 사용하고 싶을때), Q 객체를 사용할 수 있습니다.


Q객체는 Keyword arguments 들을 encapsulate 하기 위해 쓰이는 객체입니다. 

(encapsulate=캡슐화 : 연관 있는 변수와 함수를 하나의 단위로 묶는 것, 이는 데이터의 번들링을 의미함, 파이썬의 경우에 이 번들링은 클래스를 통해 이뤄지고, 이 클래스의 인스턴스 생성으로 클래스 안에 포함된 변수와 메소드에 접근할 수 있게 됨) 

(Q객체의 경우는 keyword argument - 정렬의 기준이 되는 값 - 과 field lookup을 통한 sql WHERE 절 - 즉 메소드를, Q클래스를 상속받은 Q객체를 통해서 번들링 한다고 이해했습니다)

이 keyword arguments들은 "Field lookups"를 통해 특정화되는데, 이에 대해서는 후술하겠습니다.


Q 객체는 & 와 | 를 통해서 합쳐질 수 있습니다. 두개의 Q 객체는 & , | 의 오퍼레이터를 거쳐 하나로 합쳐집니다. 

1
Q(question__startswith='where') | Q(question__startswith='When)
예를 들어 위 코드는 두개의 question__startswith 쿼리들을 'OR'로 합친 하나의 Q객체를 만들어냅니다.

keyword arguments들을 받는 lookup function들은(filter()나 exclude(), get() 등) 하나 이상의 Q객체를 positional arguments로 받을 수 있습니다. lookup function에 여러개의 Q 객체를 준다면 각각의 argument들은 AND될 것입니다.


1
2
3
4
Drink.objects.get(
    Q(name__startswith='Nok'),
    Q(caffeine=0 | Q(caffeine=1)
)
get()안의 두개의 Q객체가 있는데, 이들은 AND조건으로 연결된다는 뜻입니다.
 

Lookup 함수들은 Q객체와 keyword argument을 함께 사용할 수 있습니다. 마찬가지로 lookup 함수에 주어지는 모든 arguments는 AND로 연결됩니다. 
이때 주의해야 할 것은 Q객체가 keyword arguments 보다 먼저 와야 된다는 것입니다.


1
2
3
4
Drink.objects.get(
    name__startswith='Nok',
    Q(caffeine=0 | Q(caffeine=1)
)
이러한 코드 구성은 유효하지 않다는 뜻입니다.


앞서 field lookup에 대해 얘기했었죠,
Field lookups는 SQL WHERE 절의 의미를 구체화하는 방식입니다.
이것들은 get() filter() exclude()등의 쿼리셋 메소드 에서 사용됩니다

field__lookuptype=value 
의 형식으로 field lookup을 사용합니다.
lookup에 사용된 field의 이름은 모델 필드의 이름과 같아야 합니다.

다만, 예외가 있습니다. Foreign key의 경우 필드네임을 접미사에 _id를 붙임으로써 나타낼 수 있습니다. 이 때, value 값은 foreign 모델의 pk의 raw value를 포함하고 있어야 합니다.
Drink.objects.filter(review_id=4)
이런식으로 말이죠.

다양한 필드 lookup reference가 있습니다. 

https://docs.djangoproject.com/en/4.0/ref/models/querysets/#field-lookups

해당 링크에서 이에 대해 확인해 볼 수 있습니다. 이들 중 프로젝트에 사용됐던 몇가지 field lookup reference를 알아보도록 하겠습니다.


price_lower과 price_upper 사이의 range 안에 price가 있는지 확인하는 코드입니다. 미리 선언해두었던 q = Q()에 add를 통해서 조건을 더해줍니다.



사용자가 카테고리를 여러가지 선택할 수 있도록 구현해야 했기에 str로 값을 받고, 이를 쉼표로 나눠준 리스트를 만듭니다.  

저 __name 부분에서 매우 애를 먹었는데, __name을 해주지 않으면 id값으로만 필터링이 가능했습니다. erd를 보시면 아시겠지만, category(클래스명은 Category)는 테이블 이름이지, 테이블 내의 컬럼명이 아닙니다. 따라서 그냥 category만 써줬을 경우에는 pk인 id 값을 통해서 구분을 지어준 것이 아닌가 생각이 듭니다. category 테이블의 컬럼명인 name을 __name이렇게 연결해 주고서야 name을 통해서 필터링이 가능해졌습니다. 
__in은 categories라는 iterable (list, tuple , queryset 등) 안에 category__name 이 있는지 확인하기 위해 사용된 filed lookup입니다.