개발기록장

[Django] QuerySet 메소드 정리 (1) - 연결된 테이블에서 데이터 조회(정참조/역참조) 본문

TIL/Django

[Django] QuerySet 메소드 정리 (1) - 연결된 테이블에서 데이터 조회(정참조/역참조)

yangahh 2021. 1. 21. 18:06

 

 

Django models.py 파일로 테이블을 만들어보고 Django Shell에서 QuerySet 메소드로 데이터를 조회해보자.

 


 

예제로 스타벅스 홈페이지의 음료 메뉴를 모델링해보았다.(메뉴가 음료인 것만 간략히)

 

1. 현재 프로젝트 구조와 테이블 관계

현재 프로젝트 구조는 다음과 같다.

 

- 프로젝트 명 : westarbucks

- 작업 할 app 명 : products

 

 

그리고 아래 그림은 테이블 관계를 간략하게 표현한 것이다.

 

 

 

2. products/models.py

from django.db import models


class Menu(models.Model):
    name = models.CharField(max_length=20)

    def __str__(self):  # shell에서 보이는 정보
        return f'{self.name}'

    class Meta:  
        db_table = 'menu'  # 실제 DB에 만들어지는 테이블 명



class Category(models.Model):
    name = models.CharField(max_length=20)
    menu = models.ForeignKey('Menu', on_delete=models.SET_NULL, null=True)

    def __str__(self):
        return f'{self.name}'

    class Meta:
        db_table = 'categories'



class Drink(models.Model):
    category     = models.ForeignKey('Category', on_delete=models.SET_NULL, null=True)
    korean_name  = models.CharField(max_length=45)
    english_name = models.CharField(max_length=45)
    description  = models.TextField()
    is_new       = models.SmallIntegerField(default=0)

    def __str__(self):
        return f'{self.korean_name}'

    class Meta:
        db_table = 'drinks'



class Image(models.Model):
    drink     = models.ForeignKey('Drink', on_delete=models.CASCADE)
    image_url = models.CharField(max_length=2000)

    def __str__(self):
        return f'{self.drink} : {self.image_url}'

    class Meta:
        db_table = 'images'



class Nutrition(models.Model):
    drink            = models.OneToOneField('Drink', on_delete=models.CASCADE)
    size             = models.CharField(max_length=20, null=True)
    size_ml          = models.IntegerField(null=True)
    size_fluid_ounce = models.IntegerField(null=True)
    one_serving_kcal = models.DecimalField(max_digits=6, decimal_places=2, null=True)
    sodium_mg        = models.DecimalField(max_digits=6, decimal_places=2, null=True)
    saturated_fat_g  = models.DecimalField(max_digits=6, decimal_places=2, null=True)
    sugers_g         = models.DecimalField(max_digits=6, decimal_places=2, null=True)
    protein_g        = models.DecimalField(max_digits=6, decimal_places=2, null=True)
    caffeine_mg      = models.DecimalField(max_digits=6, decimal_places=2, null=True)

    def __str__(self):
        return f'{self.drink} nutritions'

    class Meta:
        db_table = 'nutritions'



class Allergy(models.Model):
    name = models.CharField(max_length=45)

    def __str__(self):
        return f'{self.name}'

    class Meta:
        db_table = 'allergies'



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

    def __str__(self):
        return f'{self.drink} : {self.allergy}'

    class Meta:
        db_table = 'allergy_drink'

 

 

 

3. Shell에서 데이터 조회

 

- Django가 import된 python shell 실행

python manage.py shell

 

- 데이터 insert

>>> Menu.objects.create(name="음료")

>>> Category.objects.create(name="콜드 브루", menu_id=1)

>>> Drink.objects.create(korean_name="나이트로 바닐라 크림", category_id=1)

** 이렇게 연결된 테이블에서는 설정된 관계에 따라 순차적으로 데이터를 넣어주는게 좋다.(메뉴 > 카테고리 > 음료)

 

이런식으로 샘플 데이터를 넣어주었고 샘플데이터는 아래와 같다.

 

 

- 정참조(Forward)로 연결된 데이터 조회하기

정참조는 간편하게 '.' 으로 조회 가능하다.

예시) '나이트로 바닐라 크림'라는 음료가 속한 메뉴 이름 조회하기

>>> drink = Drink.objects.get(id=1)


>>> drink
<Drink: 나이트로 바닐라 크림>


>>> drink.korean_name
'나이트로 바닐라 크림'


# '나이트로 바닐라 크림'이 속한 카테고리 정보 조회
>>> drink.category
<Category: 콜드 브루>


# '나이트로 바닐라 크림'이 속한 카테고리의 이름만 조회
>>> drink.category.name
'콜드 브루'


# '나이트로 바닐라 크림'이 속한 카테고리가 속한 메뉴 정보 조회
>>> drink.category.menu
<Menu: 음료>


# '나이트로 바닐라 크림'이 속한 카테고리가 속한 메뉴의 이름만 조회
>>> drink.category.menu.name
'음료'

 

 

- 역참조(Backward)로 연결된 데이터 조회하기

해당 객체를 참조하고 있는 다른 객체가 ForeignKey를 가지고 있거나 다대다 관계인 경우, 해당 객체 기준에서 사용하는 방법이다. (누가 날 바라보고 있는지 모르는 쪽)

즉, 부모테이블에서 자식 테이블의 특정 데이터를 조회할 때를 말한다.

 

Django에서 역참조를 할 수 있게 해주는 도구를 사용한다.

예시) id가 1인 메뉴를 참조하고 있는 음료 데이터 중 첫번째 데이터만 조회하기

>>> menu = Menu.objects.get(id=1)


>>> menu.category_set
<django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager object at 0x7fde98116820>
# 해당 객체가 참조하는 객체명(소문자)_set은 reverse_many_to_one_manager라고 나온다.
# 즉, 역참조를 할 수 있게 해주는 도구이다.


# id가 1인 메뉴를 참조하고 있는 카테고리의 모든 데이터 조회
>>> menu.category_set.all()
<QuerySet [<Category: 콜드 브루>, <Category: 블렌디드>, <Category: 스타벅스 피지오>, <Category: 티(티바나)>]>


# id가 1인 메뉴를 참조하고 있는 카테고리의 모든 데이터에서 첫번째 데이터만 조회
>>> menu.category_set.all()[0]
<Category: 콜드 브루>


# id가 1인 메뉴를 참조하고 있는 카테고리의 첫번째 데이터를 참조하고 있는 음료의 모든 데이터 조회
>>> menu.category_set.all()[0].drink_set.all()
<QuerySet [<Drink: 나이트로 바닐라 크림>, <Drink: 나이트로 쇼콜라 클라우드>]>


# id가 1인 메뉴를 참조하고 있는 카테고리의 첫번째 데이터를 참조하고 있는 음료에서 첫번째 데이터만 조회(이름만)
>>> menu.category_set.all()[0].drink_set.all()[0].korean_name
'나이트로 바닐라 크림'