[TIL. 34] Django model API (데이터 가져오기-정참조, 역참조)

신지원·2021년 3월 30일
3
post-thumbnail

Queryset?

쿼리셋(QuerySet)은 전달받은 모델의 객체 목록을 말한다. 데이터베이스로부터 데이터를 읽고 필터를 걸거나 정렬 등을 할 수 있다. 리스트와 구조는 같지만 파이썬 기본 자료구조가 아니기에 읽고 쓰기 위해서는 자료형 변환을 해줘야한다. 쿼리셋은 데이터베이스의 여러 레코드(row)를 나타낸다.

from .models import Book
Book.objects.all()	# Book 모델(테이블)의 모든 데이터를 갖고와라!
<QuerySet [<Book: 책 제목1>, <Book: 책 제목2>]>

여기서 objects 는 Model Manager이다. DB와 Django Model 사이의 Query Operation(질의연산)의 인터페이스 역할을 한다. 이 때, objects 를 사용해서 다수의 데이터를 갖고오는 함수를 사용할 때, 반환되는 객체가 QuerySet이다.

이 Manager 각 모델(클래스)가 최소 하나씩 갖고 있다.
Person.objects 의 의미는, objects라는 이름의 manager가 Person 데이터베이스를 QuerySet 형태로 만들겠다는 의미이다. 그 QuerySet에서 데이터를 검색하게 만들 수 있다.

CRUD

쿼리셋을 return 하지 않는 Method

  • create()

  • .get()
    하나의 row만 가져올때 사용.
    Question.objects.get(pk=1)
    pk가 1인 값을 가져옴
    하나의 값만 가져옴
    <객체를 리턴>

  • .update()
    지정된 필드에 대해 업데이트 쿼리를 수행하고 일치하는 행수를 반환한다.

In  : Category.objects.filter(name='탄산').update(name='콜드브루')
Out : 2 #총 업데이트된 row 개수
  • .delete()
    데이터 삭제할때 사용한다.
    객체일때 사용가능하다.
    먼저 삭제할 Row 객체를 불러온 뒤에 사용한다.
fb = Feedback.objects.get(pk=2)
fb.delete()
  • .save()
    insert, update를 수행하는 method로, 단일 객체에 대해서 업데이트를 수행할 때 사용.
In  : category = Category.objects.get(id=2)
Out : <Category: Category object (2)>

In  : category.name
Out : '브루드커피'

In  : category.name = 'new name'
In  : category.save()

In  : category.name
Out : 'new name'
  • exist()
    filter과 함께 사용해서 조건에 맞는 데이터가 있는 조회 해서 있으면 True를 return 없으면 False return
In  : Category.objects.filter(name='브루드커피').exists()
Out : True

쿼리셋을 return하는 method

  • all()
    테이블 데이터를 전부 가져 오기 위한 메서드
    <쿼리셋을 리턴>

  • filter, exclude()
    -> .filter()
    특정 조건에 맞는 Row들을 가져오기 위해 사용
    값이 2개이상이여도 가져올 수 있음
    아무조건 없을 시 all과 동일
    <쿼리셋을 리턴>

  • values()
    iterable로 사용될때 모델 인스턴스가 아닌 dictionary를 포함하는 쿼리셋을 반환한다.

  • values_list()
    튜플을 반환한다.


  • add()
    다대다인 관계일때 사용.
d1 = Drink.objects.get(id=1)
a1 = Allergy.objects.get(id=1)

#drink와 allergy는 다대다 관계이고, 중간 테이블(DrinkAllergy 테이블)을 거치지 않고 drink를 통해 allergy에 정보를 담을 수 있다. 
d1.allergies.add(a1)
  • 객체 생성
    q = Question(question_text="What's new?", pub_date=timezone.now())
  • 쉘 사용할때
    테스트 해보고 싶을때

테스트 해보면 코드들을 보아서 뷰에다가 넣는...?
데이터가 나오는지 안나오는지 쳐보는거

쉘에서 데이터가 넣어졌는지 안넣어졌는지 하나하나 쳐보면서 확인 해보면서 나중에 그것들을 이제 view에다가 넣는것!

쉘꺼주면 내용 다 날라감(변수같은거)

object에만 사용할 수 있는 attribute가 있기 때문에 잘 알아야된다.

Many to Many 관계를 설정해주면 더 좋은 이유는?

중간 테이블 AllergyDrink를 만들어주고 Allergy와 Drink를 FK로 연결해줘도 사실 데이터를 읽고 가져오는데에는 문제가 없다. 근데 굳이 many to many 로 관계설정을 해주는 이유는 뭘까?

1) 데이터를 넣을때 중간테이블을 거치지 않아도 되서 훨씬 간단하다.
2) 데이터를 가져올 때는 최소한의 데이터베이스를 거쳐 가져오는 것이 좋은데 MTM연결을 해준 경우에는 중간테이블을 거치지 않아도 데이터를 가져올 수 있어 그 과정이 훨씬 간단해진다.

더 자세하게 보기

many to many관계를 넣어줄때 장점은 중간테이블을 거치지 않고 언제든지 데이터를 쉽게 추가하고 가져올 수 있다는 장점이 있다.

데이터 가져오기 (참조)

class Drink(models.Model):
    korean_name = models.CharField(max_length=45)
    english_name = models.CharField(max_length=45)
    description = models.TextField()
    category = models.ForeignKey('Category', on_delete=models.CASCADE)
    allergies = models.ManyToManyField(Allergy, through = "AllergyDrink")
    
class Category(models.Model):
    name = models.CharField(max_length=45)
    menu = models.ForeignKey('Menu', on_delete=models.CASCADE)
    
class Allergy(models.Model):
    name = models.CharField(max_length=45)

class AllergyDrink(models.Model):
    allergy = models.ForeignKey('Allergy',on_delete=models.CASCADE)
    drink = models.ForeignKey('Drink',on_delete=models.CASCADE)

정참조인 경우

1) Drink를 통해서 drink가 어떤 category 참조하고 있는지 쉽게 알 수 있다.(Drink -> Category)

In [42]: d1 = Drink.objects.get(id=1)
In [43]: d1.category.name
Out[43]: '콜드 브루 커피'
# 또는 이렇게 
In [47]: Drink.objects.get(id=1).category.name
Out[47]: '콜드 브루 커피'

2) dirnk를 통해 allergies를 가져오는 경우 (Drink -> Allergy) (++many to many 관계임)

# 1개의 알러지만 가지고 있는 경우
In [2]: d1 = Drink.objects.get(id=1)

In [4]: d1.allergies.get()
Out[4]: <Allergy: Allergy object (2)>

In [5]: d1.allergies.values()
Out[5]: <QuerySet [{'id': 2, 'name': '우유'}]>

In [6]: d1.allergies.get().name
Out[6]: '우유'

# 2개 이상의 알러지를 가지고 있는 row
In [7]: d10 = Drink.objects.get(id=10)

In [8]: d10.allergies.values()
Out[8]: <QuerySet [{'id': 1, 'name': '대두'}, {'id': 2, 'name': '우유'}]>

In [9]: a = d10.allergies.values()

In [10]: for i in a:
    ...:     print(i)
    ...: 
{'id': 1, 'name': '대두'}
{'id': 2, 'name': '우유'}

In [11]: for i in a:
    ...:     print(i['name'])
    ...: 
    ...: 
대두
우유

# 또 다른 방법 
In [42]: Drink.objects.get(id=1).allergies
Out[42]: <django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager at 0x7f8b6ec95190>

In [43]: Drink.objects.get(id=1).allergies.get()
Out[43]: <Allergy: Allergy object (2)>

In [44]: Drink.objects.get(id=1).allergies.get().name
Out[44]: '우유'

역참조인 경우

related name을 설정해주지 않았으면 _set이 붙으면 역참조 데이터임을 알 수 있다.

✔️ 역참조인 관계
Fk를 지정해주지 않은 class 그러니깐 즉, pk가 있는 쪽의 테이블!

1) Cateogory 역참조 Drink를 가져오기 (Drink -> Category : 원래는 이렇게 참조 하고 있는 관계임)

In [11]: Category.objects.get(id=1).drink_set
Out[11]: <django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager at 0x7fe9e0c6dc90>

In [12]: Category.objects.get(id=1).drink_set.all()
Out[12]: <QuerySet [<Drink: Drink object (1)>, <Drink: Drink object (2)>, <Drink: Drink object (7)>, <Drink: Drink object (8)>, <Drink: Drink object (11)>, <Drink: Drink object (12)>]>
In [13]: c = Category.objects.get(id=1).drink_set.all()
In [16]: for ca in c:
    ...:     print(ca.korean_name)
    ...: 
    ...: 
    ...: 
나이트로 바닐라 크림
미드나잇 베르가못 콜드 브루
벨젯 다크 모카 나이트로
프렌치 애플 타르트 나이트로
프렌치 애플 타르트 나이트로
카푸치노

2) allergy를 통해 drink 가져오는 방법 (Drink-> Allergy : 원래는 이렇게 참조 하고 있는 관계임)(++ many to many 관계임)

In [26]: Allergy.objects.get(id=2).drink_set.values()
Out[26]: <QuerySet [{'id': 1, 'korean_name': '나이트로 바닐라 크림', 'english_name': 'Nitro Vanilla Cream', 'description': '부드러운 목넘김의 나이트로 커피와 바닐라 크림의 매력을 한id': 2, 'korean_name': '미드나잇 베르가못 콜드 브루', 'english_name': '', 'description': '', 'category_id': 1}, {'id': 3, 'korean_name': '딸기 딜라이트 요거트 블렌디드', 'english_n'', 'category_id': 2}, {'id': 5, 'korean_name': '쿠키 블루베리 잼 머핀', 'english_name': '', 'description': '', 'category_id': 3}, {'id': 7, 'korean_name': '벨젯 다크 모카 나이트로'', 'description': '', 'category_id': 1}, {'id': 10, 'korean_name': '돌체 라떼', 'english_name': '', 'description': '', 'category_id': 3}]>

In [27]: b = Allergy.objects.get(id=2).drink_set.values()

In [28]: for bs in b:
    ...:     print(bs)
    ...: 
{'id': 1, 'korean_name': '나이트로 바닐라 크림', 'english_name': 'Nitro Vanilla Cream', 'description': '부드러운 목넘김의 나이트로 커피와 바닐라 크림의 매력을 한번에 느껴보세요!', 'category_id': 1}
{'id': 2, 'korean_name': '미드나잇 베르가못 콜드 브루', 'english_name': '', 'description': '', 'category_id': 1}
{'id': 3, 'korean_name': '딸기 딜라이트 요거트 블렌디드', 'english_name': '', 'description': '', 'category_id': 2}
{'id': 5, 'korean_name': '쿠키 블루베리 잼 머핀', 'english_name': '', 'description': '', 'category_id': 3}
{'id': 7, 'korean_name': '벨젯 다크 모카 나이트로', 'english_name': '', 'description': '', 'category_id': 1}
{'id': 10, 'korean_name': '돌체 라떼', 'english_name': '', 'description': '', 'category_id': 3}

In [29]: for bs in b:
    ...:     print(bs['korean_name'])
    ...: 
    ...: 
나이트로 바닐라 크림
미드나잇 베르가못 콜드 브루
딸기 딜라이트 요거트 블렌디드
쿠키 블루베리 잼 머핀
벨젯 다크 모카 나이트로
돌체 라떼

all, get, values

  • all, get ,values 차이
    values는 딕셔너리 형태로 반환한다.

    field 값을 바로바로 불러오도록 하기 위해서는 object로 반환되어야 한다.

하나의 값만 가지고 있는 경우

get은 object를 반환하기 때문에 바로 사용할 수 있다. (근데 string 타입임..)

여러 값을 가지고 있는 경우

이때 고려해볼 수 있는건 all,values이다.

1) all을 사용하는 경우

  • all를 사용하면 query set을 반환하고 이때도 필드값 바로 불러 오는것은 불가능하다.
  • for문을 사용하면 queryset을 object로 바꿀 수 있다. 그래서 for 문을 사용해서 object 형태로 바꾸어 준 다음에 field값을 바로 불러올 수 있다.

2) values를 사용하는 경우

  • values는 딕셔너리 형태로 값을 가져온다.
    values는 처음에는 딕셔너리를 담고 있는 queryset을 리턴하고 이를 for문을 사용해서 딕셔너리 형태로 바꾸어 줄 수 있다. 따라서 .(dot)을 사용해서 가져오지는 못하고 ['key_name']을 통해서만 가져올 수 있다.

++ 다른 방법!!!
values()를 사용해도 for 사용안하고 values('필드값') 이런식으로도 사용할 수 있다.

이런식으로 리스트로도 가져올 수 있음

💡 object의 형태일때만 .(dot)을 사용해서 field 값을 바로 가져오는 것이 가능!

django 공식문서- Queryset API

0개의 댓글