Django Shell에서 모델 필터링
모델 필터링은 해당 모델에서 내가 사용하고 싶은 레코드만 필터링해 조회하는 것을 말한다. Django Shell에서는 어떤 방법으로 모델 필터링을 할 수 있을까?
1. get() 메서드
>>> from polls.models import *
# id값으로 조회
>>> Question.objects.get(id=1)
<Question: 가장 추천하는 가을 캠핑장은 어디인가요?>
# 텍스트 시작값으로 조회
>>> Question.objects.get(question_text__startswith='야구')
<Question: 야구 vs 축구>
>>> q= Question.objects.get(question_text__startswith='야구')
>>> q.pub_date
datetime.datetime(2024, 10, 7, 5, 17, 5, 82114, tzinfo=datetime.timezone.utc)
# pub_date의 초 단위로 조회
>>> Question.objects.get(pub_date__second=5)
<Question: 야구 vs 축구>
위와 같이 get() 메서드를 이용하면 여러가지 원하는 조건 별로 필터링된 레코드를 얻을 수 있다. 하지만 get() 메서드는 아래와 같은 현상을 보이기도 한다.
>>> Question.objects.get(pub_date__year=2024)
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/home/minding/anaconda3/envs/django_project/lib/python3.9/site-packages/django/db/models/manager.py", line 87, in manager_method
return getattr(self.get_queryset(), name)(*args, **kwargs)
File "/home/minding/anaconda3/envs/django_project/lib/python3.9/site-packages/django/db/models/query.py", line 640, in get
raise self.model.MultipleObjectsReturned(
polls.models.Question.MultipleObjectsReturned: get() returned more than one Question -- it returned 3!
위 코드는 연도값이 2024인 항목을 조회한다. 하지만 해당 조건에 맞는 레코드가 여러 개일 경우 에러가 발생한다. 에러 코드를 읽어보니, get() 메서드는 해당 조건에 맞는 레코드 하나만 불러올 수 있다고 한다.
2. filter() 메서드
레코드를 여러개 불러오기 위해서는 filter() 메서드를 사용해주어야 한다.
>>> Question.objects.filter(pub_date__year=2024)
<QuerySet [<Question: 가장 추천하는 가을 캠핑장은 어디인가요?>, <Question: 야구 vs
축구>, <Question: abc???>]>
# count()를 통해 레코드 개수를 알 수도 있다.
>>> Question.objects.filter(pub_date__year=2024).count()
3
기본적으로 여러 개를 반환할 경우, QuerySet 형태로 반환된다. (주의: 무조건 1개씩 연결되는 경우 Queryset 형태가 아님.) 따라서 .count() 등의 메서드를 이용해 개수를 셀 수도 있다.
QuerySet은 필터링을 거쳤을 때 사용된 SQL 쿼리문에 대해서도 조회할 수 있다.
# SQL 쿼리문 출력
>>> print(Question.objects.filter(pub_date__year=2024).query)
SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."pub_date" BETWEEN 2024-01-01 00:00:00 AND 2024-12-31 23:59:59.999999
# 문자열 필터링의 경우
>>> print( Question.objects.filter(question_text__startswith='야구').query)
SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."question_text" LIKE 야구% ESCAPE '\'
# question에 연결된 choice 조회할 경우
>>> q = Question.objects.get(pk=1)
>>> q.choice_set.all()
<QuerySet [<Choice: 포천>]>
>>> print(q.choice_set.all().query)
SELECT "polls_choice"."id", "polls_choice"."question_id", "polls_choice"."choice_text", "polls_choice"."votes" FROM "polls_choice" WHERE "polls_choice"."question_id"
= 1
2024년 조건에 해당하는 레코드를 찾기 위해 WHERE절을 사용해 찾는다는 것을 알 수 있다. django에서는 이런 방식으로 SQL 쿼리문을 자동으로 작성해준다.
3. filter() 메서드를 활용해 여러가지 방법으로 필터링하기
위의 예시에서 필터링을 이용해 조회한 것 이외에도 여러가지 조건을 선택할 수 있다. 장고 공식 문서를 참고하면, 여러가지 조건을 걸 수 있다는 것을 알 수 있다. (공식문서 링크)
이 중 대표적인 몇 가지는 아래와 같다.
- contains - 특정 문자열이 포함된 레코드 조회
- gt - 특정 값보다 큰 레코드 조회
- regex - 정규표현식을 이용해 필터링
>>> from polls.models import *
# contains - 특정 문자열이 포함된 레코드 조회
>>> Question.objects.filter(question_text__contains='야구')
# gt - 해당 값보다 큰 것을 조회(초과)
Choice.objects.filter(votes__gt=0)
# regex - 정규표현식을 이용해 필터링
# '야구'가 들어가는 항목이 2개가 있음
>>> Question.objects.all()
<QuerySet [<Question: 가장 추천하는 가을 캠핑장은 어디인가요?>, <Question: 야구 vs
축구>, <Question: abc???>, <Question: 야구를 왜 좋아하시나요?>]>
# '야구'가 처음 나오고 그 다음 '왜'가 나오는 문자열 찾기
>>> Question.objects.filter(question_text__regex=r'^야구.*왜')
<QuerySet [<Question: 야구를 왜 좋아하시나요?>]>
# 쿼리문 살펴보기
>>> print(Question.objects.filter(question_text__regex=r'^야구.*왜').query)
SELECT "polls_question"."id", "polls_question"."question_text", "polls_question"."pub_date" FROM "polls_question" WHERE "polls_question"."question_text" REGEXP ^야구.*왜
4. 모델 관계 기반 필터링
모델 사이에 FK 등으로 관계가 연결되어 있을 경우 이를 활용한 필터링도 가능하다.
필터링을 위해 위와 같이 여러 개의 choice를 생성했다. 또한, 답변 앞에 질문이 어떤거였는지 __str__ 함수를 다음과 같이 조정했다.
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return f'[{self.question.question_text}] {self.choice_text}'
이제 Shell을 다시 시작해 question 값을 이용해 choice를 필터링해보자.
>>> from polls.models import *
# choice에 연결된 question의 text가 '가장'으로 시작하는 choice 레코드만 조회
>>> Choice.objects.filter(question__question_text__startswith='가장')
<QuerySet [<Choice: [가장 추천하는 가을 캠핑장은 어디인가요?] 포천>, <Choice: [가장
추천하는 가을 캠핑장은 어디인가요?] 가평>, <Choice: [가장 추천하는 가을 캠핑장은
어디인가요?] 강릉>]>
위 choice 레코드들 중 question의 text가 '가장'으로 시작할 때에만 레코드를 조회하도록 했다. 다른 레코드가 보이지 않는 것을 알 수 있다. 해당 쿼리를 살펴보면, INNER JOIN을 통해 해당 조건을 필터링하는 것을 알 수 있다.
>>> print(Choice.objects.filter(question__question_text__startswith='가장').query)
SELECT "polls_choice"."id", "polls_choice"."question_id", "polls_choice"."choice_text", "polls_choice"."votes" FROM "polls_choice" INNER JOIN "polls_question" ON ("polls_choice"."question_id" = "polls_question"."id") WHERE "polls_question"."question_text" LIKE 가장% ESCAPE '\'
반대로 .exclude() 메서드를 이용하면 해당 문자열을 제외한 모두를 조회하는 것도 가능하다.
>>> Question.objects.exclude(question_text__startswith='가장')
<QuerySet [<Question: 야구 vs 축구>, <Question: abc???>, <Question: 야구를 왜 좋아
하시나요?>]>
5. 필터링된 레코드들 수정 / 삭제하기
.update() - 필터링된 레코드 모두 수정
# 미리 '포천'에대한 votes값을 3으로 바꿔 놓음
>>> Choice.objects.filter(votes__gt=0)
<QuerySet [<Choice: 포천>]>
# update() 메서드를 이용해 필터링된 레코드의 votes 칼럼값 변경
>>> Choice.objects.filter(votes__gt=0).update(votes=0)
1
# 다시 조회
>>> Choice.objects.filter(votes__gt=0)
<QuerySet []>
.delete() - 필터링된 레코드 모두 수정
# 모두 조회
>>> Choice.objects.all()
<QuerySet [<Choice: 포천>, <Choice: c>, <Choice: 강릉>]>
# votes가 0 초과하는 레코드 조회
>>> Choice.objects.filter(votes__gt=0)
<QuerySet [<Choice: 강릉>]>
# votes가 0 초과하는 레코드 삭제 (1건 삭제됨)
>>> Choice.objects.filter(votes__gt=0).delete()
(1, {'polls.Choice': 1})
# 모두 조회
>>> Choice.objects.all()
<QuerySet [<Choice: 포천>, <Choice: c>]>
'Minding's Programming > Django' 카테고리의 다른 글
[Django] 폼(Forms) (0) | 2024.10.08 |
---|---|
[Django] 뷰(views)와 템플릿(templates) (0) | 2024.10.08 |
[Django] 모델 메소드(Model Method) (1) | 2024.10.08 |
[Django] Django Shell 사용해보기 (0) | 2024.10.07 |
[Django] Django 기본 설정 (0) | 2024.10.07 |