TIL #72 : [Django] Query Set Methods (1/2)

셀레스틴 허·2021년 2월 13일
0
post-thumbnail

QuerySet을 하나도 몰랐을 때... 그 때 정말 카오스였다🤯 이제 조금 익숙(?)해졌으니 복습겸 한번 정리하고 싶었다.

✅ Django Query Set Methods

1. object 가져오기(.objects()), object 다 가져오기(.all())

>>> from user.models import *
>>> from posting.models import *
>>> User.objects
<django.db.models.manager.Manager object at 0x7f818a47d9a0>
>>> u = User(name='mousecat')
>>> u.objects
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/mjhuh/miniconda3/envs/westagram/lib/python3.9/site-packages/django/db/models/manager.py", line 179, in __get__
    raise AttributeError("Manager isn't accessible via %s instances" % cls.__name__)
AttributeError: Manager isn't accessible via User instances
>>> all_entries = User.objects.all()
>>>

QuerySet은 model의 manager로 가져오는 것이다. 디폴트로 objects이라고 설정되어 있다. 여기서 우리는 .all()을 이용해 모든 모든 User objects들을 QuerySet을 불러올 수 있다.

2. 필터를 이용해 특정 object 뽑기(filter(), exclude())

많은 object 중 특정 subset만 추출해야할 때 사용하는 메소드.

>>> User.objects.filter(username='mousecat')
<QuerySet [<User: User object (3)>]>
>>> User.objects.all().filter(username="mousecat")
<QuerySet [<User: User object (3)>]>
>>> User.objects.exclude(username="mousecat")
<QuerySet [<User: User object (27)>, <User: User object (31)>, <User: User object (1)>, <User: User object (2)>, <User: User object (32)>]>

default manager class으로 똑같은 결과물을 얻을 수 있다. filter를 매번할 때마다 고유의 QuerySet을 만드는 것이다. 이를 재사용, 저장, 사용할 수 있다.

** QuerySet은 lazy하기 때문에 직접 부르지(print())않는 한 정보를 가져오지 않는다.

3. 특정 게시물 하나 가져오기(.get())

filter()는 항상 단 하나의 객체만 그 Query에 맞아도 QuerySet을 준다. 하지만 만일 하나의 객체만 Query와 맞다는 것을 알면 get()을 메소드를 이용해 바로 객체를 가져올 수 있다.

>>> a = User.objects.get(id=1)
>>> print(a)
User object (1)

get, filter둘다 query expression으로만 쓸 수 있다. 이 둘의 가장 큰 차이는 get()은 맞는 객체가 안나올 시 DoesNotExist exception이 나온다. 해당 조건과 맞는 객체가 하나 보다 더 많을 시 또 MultipleObjectsReturned을 띄울 것이다.

>>> b = User.objects.get(id=100)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Users/mjhuh/miniconda3/envs/westagram/lib/python3.9/site-packages/django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "/Users/mjhuh/miniconda3/envs/westagram/lib/python3.9/site-packages/django/db/models/query.py", line 429, in get
    raise self.model.DoesNotExist(
user.models.User.DoesNotExist: User matching query does not exist.

4. value 값 dictionary로 가져오기(.values())

.values()는 iterable으로 쓸 시 model instance가 아닌 딕셔너리를 반환한다. 각 딕셔너리는 객체이며, 각 key는 attribute의 이름이다.

>>> b = User.objects.filter(id=1).values()
>>> print(b)
<QuerySet [{'id': 1, 'name': 'chaehyunnim', 'phone': '010-1234-5678', 'email': 'cnim@naver.com', 'password': '$2b$12$QFc94pzweFzKrUPCepBHb.Z1tCEodzl7CrJGQJssQoKImWyWNjiXS', 'username': 'goyangee1004', 'created_at': datetime.datetime(2021, 2, 4, 14, 43, 40, 536359, tzinfo=<UTC>), 'modified_at': datetime.datetime(2021, 2, 4, 14, 43, 40, 536387, tzinfo=<UTC>)}]>

.values*fields라는 postional agruments를 받기도 하는데, 이 때 특정 field 이름을 적으면 해당 field를 SELECT으로 가져올 수 있다.

>>> b = User.objects.filter(id=1).values('id', 'phone')
>>> print(b)
<QuerySet [{'id': 1, 'phone': '010-1234-5678'}]>

또한 .values는 FK와 연결된 field를 가져올 경우 그 id값을 가져온다.

>>> a = Posting.objects.filter(id=2).values()
>>> print(a)
<QuerySet [{'id': 2, 'username_id': 1, 'image_url': 'https://imageurlwillchange.com', 'created_at': datetime.datetime(2021, 2, 5, 0, 48, 7, 298620, tzinfo=<UTC>), 'description': None}]>

여기서 username은 FK이다. id값인 1만 나온 것을 확인할 수 있다.

5. value 값 list로 가져오기(.values_list())

values()와 매우 비슷하지만 딕셔너리 대신 iterate할 때마다 튜플을 반환한다. 각 튜플은 해당 field에 해당되는 value값, 또는 values_list()에 부여된 expression을 포함하고 있다.

>>> a = Posting.objects.filter(id=2).values_list()
>>> print(a)
<QuerySet [(2, 1, 'https://imageurlwillchange.com', datetime.datetime(2021, 2, 5, 0, 48, 7, 298620, tzinfo=<UTC>), None)]>
>>> a = Posting.objects.filter(id=2).values_list(named=True)
>>> print(a)
<QuerySet [Row(id=2, username_id=1, image_url='https://imageurlwillchange.com', created_at=datetime.datetime(2021, 2, 5, 0, 48, 7, 298620, tzinfo=<UTC>), description=None)]>
>>> a = Posting.objects.filter(id=2).values_list(flat=True)
>>> print(a)
<QuerySet [2]>
>>>

flat=True는 값들이 하나의 value로 반환된다는 것이고, named=True는 이름이 포함된 튜플을 가져오고 싶을 때 쓴다.

** 이 둘은 model instance를 만들지 않고 subset을 생성할 때 유리하다. 그러나 m2m 또는 muli-value 관계와 함께 사용할 시 별로 추천하지 않는다. 그 이유는.. 하나의 행, 하나의 객체 룰이 망가지기 때문이다!

5. object 생성하기(.create())

장고 쉘에서 객체를 만들고, 저장할 수 있다. 아래 예시와 같이 두가지 방법으로 만들 수 있다.

>>> a = Posting.objects.create(username_id=2, image_url="https://abc.com/123", description="예시입니당")
>>> a = Posting(username_id=1, image_url="https://image.com/456", description="다음 에시입니당")
>>> a.save()

6. object 만들어..? 만들지마?(.get_or_create())

get_or_create()는 튜플을 반환한다: (object, boolean(만들었는지, 안만들었는지)). 아까 #5번 예시에서 이미 만들었던 posting 객체로 실험해보자.

>>> a = Posting.objects.get_or_create(username_id=2, image_url="https://abc.com/123", description="예시입니당")
>>> print(a)
(<Posting: Posting object (7)>, False)

두둥 이미 등록되어있기 때문에 PK=7 객체, 그리고 이미 존재하기 때문에 false라고 출력된다. 그럼 새로운 객체를 만들어보면 어떻게 나올까?

>>> a = Posting.objects.get_or_create(username_id=32, image_url="https://example.com/001", description="다다른 예시입니당")
>>> print(a)
(<Posting: Posting object (9)>, True)

posting의 PK=9로 새롭게 생성되어 들어갔으며, 아직 객체가 존재하지 않기 때문에 True라고 나왔다.

>>> a[0]
<Posting: Posting object (9)>
>>> a[0].description
'다다른 예시입니당'

이렇게 인덱싱으로 값을 추출할 수 있다.
** get_or_create(), filter(), Q objects를 사용해서 더 상세한 조건을 짤 수 있다.

key가 이미 존재하면 IntegrityError가 뜰 것이다.

** POST에만 쓰는 것이 좋다.

Reference:
https://docs.djangoproject.com/en/3.1/ref/models/querysets/
https://docs.djangoproject.com/en/3.1/topics/db/queries/
갓한아님 블로그 포스트 : https://devvvyang.tistory.com/37?category=973523

profile
Software Developer / 고통은 필연, 괴로움은 선택

0개의 댓글