All Articles

django - 스타벅스 음료 메뉴 모델링 실습

이번 실습에서 집중한 것은 데이터 테이블 생성, Foreign key 사용하기, 데이터 신규 입력, 추가, 삭제다. 먼저 아래처럼 Aquery로 모델링했다. models.py에서 어떤 클래스를 생성할지, 어느 테이블을 Foreign key로 연결할지 구조를 잡아둔 뒤 코드를 작성하면 어떤 클래스에 어떤 필드명으로 만들어야 할지 훨씬 명확해서 시행착오를 줄여준다.

aquery

Aquery 사용 예시


데이터 테이블 구조 짜기, Foreign key 사용하기

해당하는 앱의 models.py 파일에서 데이터 테이블 이름, 열(필드) 이름, 필드값이 될 데이터 타입과 속성 등을 정의해준다.

class Menu(models.Model):
  name = models.CharField(max_length=45)
  # name이라는 이름으로 열을 만드는데, 열에 들어갈 데이터는 문자열 타입, 최대 길이는 영문자 기준 45자라는 뜻이다.
  
  class Meta:
    db_table = 'menu'
  # 데이터 테이블 이름을 지정한다. 따로 정해주지 않으면 장고에서 '앱이름_클래스이름'으로 만든다.

class Category(models.Model):
  menu = models.ForeignKey(Menu, on_delete=models.CASCADE)
  # menu열은 Menu라는 클래스에서 생성한 menu 테이블 데이터를 끌어다 쓴다.
  # 모든 데이터는 고유 id값을 가지고 있기 때문에 그것을 기준으로 데이터를 매칭해준다.
  name = models.CharField(max_length=45)

  class Meta:
    db_table = 'category'

다대다 관계인 drink 테이블과 allergy 테이블은 중간 테이블인 allergy_drink 테이블로 이어준다.

class Drink(models.Model):
  category = models.ForeignKey(Category, on_delete=models.CASCADE)
  name = models.CharField(max_length=45)
  nutrition = models.ForeignKey(Nutrition, on_delete=models.SET_NULL, null=True)
  size = models.ForeignKey(Size, on_delete=models.SET_NULL, null=True)

  class Meta:
    db_table = 'drink'

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

  class Meta:
    db_table = 'allergy'

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

  class Meta:
    db_table = 'allergy_drink'

데이터 테이블 생성하기

manage.py 파일을 이용할 것이므로 manage.py 파일이 있는 위치에서 실행해준다.

# 변경 사항 감지해서 migrations 디렉토리 내에 initial 파일 생성
python manage.py makemigrations

# makemigtaions를 통해 감지한 변경 사항을 반영해 DB에 테이블을 새로 생성하거나 테이블 이름, 열 이름 등을 변경함
python manage.py migrate

데이터 신규 입력

장고에 기본으로 내장된 python shell을 사용했다. 데이터를 밀어넣기 위해 models.py 파일 안 클래스들을 최초 1회 import 해야한다.

from drinks.models import Menu, Category, 기타 클래스들 추가추가

# menu 테이블에 '음료' 데이터 입력. 이 테이블에 첫 번째로 입력된 데이터이므로 id값은 1이 된다.
>>> Menu.objects.create(name='음료')

# category 테이블의 menu 열 항목은 menu 테이블을 참조하므로 Foreign key로 끌어온다.
# '음료' 메뉴 안 '콜드 브루' 카테고리이므로 menu 테이블에서 id값이 1인 객체를 가져와서 menu 열에 할당해준다.
>>> Category.objects.create(menu=Menu.objects.filter(id=1)[0], name='콜드 브루')

입력한 데이터 확인

입력한 데이터는 장고 내장 DB인 sqlite3를 이용했다. 표 형태로 보기 위해서 사전에 아래 명령어를 입력해줬다.

.headers on
.mode column
# migrate로 생성한 모든 테이블 목록
.tables

# 결과값
django_admin_log            django_content_type
auth_group                  django_migrations
auth_group_permissions      django_session
auth_permission             auth_user
auth_user_groups            auth_user_user_permissions
menu                        category
# menu 테이블 안 모든 데이터 호출
select * from menu;

# 결과값
id          name
----------  ----------
1           음료
# category 테이블 안 모든 데이터 호출
select * from category;

# 결과값
id          name        menu_id
----------  ----------  ----------
1           콜드 브루        1

기존 데이터 행에 새로운 데이터 추가하기

drink 테이블 열에 fk 값을 추가해 size 테이블 정보를 끌어오려고 한다. 우선 models.py 파일에서 Drink 클래스를 수정한다. 참조할 테이블은 참조하는 테이블보다 반드시 위에 있어야 한다. 그 반대라면 fk로 끌어올 값이 없으므로 에러가 발생한다.

# size 테이블 추가
class Size(models.Model):
  name = models.CharField(max_length=45)
  size_ml = models.IntegerField(default=0)
  size_oz = models.IntegerField(default=0)

  class Meta:
    db_table = 'size'

# 기존 열 category, name, nutrition에 size 열 추가
class Drink(models.Model):
  category = models.ForeignKey(Category, on_delete=models.CASCADE)
  name = models.CharField(max_length=45)
  nutrition = models.ForeignKey(Nutrition, on_delete=models.SET_NULL, null=True)
  size = models.ForeignKey(Size, on_delete=models.SET_NULL, null=True)

  class Meta:
    db_table = 'drink'
# drink 테이블에서 id값이 1인 객체 불러오기
>>> drink1 = Drink.objects.get(id=1)

# drink 테이블의 size열에 size 테이블에서 가져온 데이터 추가하기
>>> drink1.size = Size.objects.filter(id=1)[0]

# 테이블에 데이터 저장
>>> drink1.save()

저장한 데이터를 다시 sqlite3에서 확인해보면 이렇게 출력된다.

select * from drink;

# 결과값
id          name         category_id  size_id     nutrition_id
----------  -----------  -----------  ----------  ------------
1           나이트로 바닐라 크림  1            1           1

참고로 size 테이블의 데이터는 아래와 같이 생성했다.

select * from size;

id          name        size_ml     size_oz
----------  ----------  ----------  ----------
1           Tall(톨)     355         12
2           Grande(그란데  473         16

이미 저장한 데이터 삭제하기

filter 명령어를 통해 객체를 특정한 뒤 삭제해준다. 만약 size 테이블에서 그란데 사이즈 데이터를 삭제하고 싶다면, id값이 2인 객체를 찾아 삭제하거나 name값이 ‘Grande(그란데)‘인 객체를 찾아 삭제하면 된다.

>>> Size.objects.filter(id=2).delete()
# 또는
>>> Size.objects.filter(name='Grande(그란데)').delete()

특정 필드값만 삭제하기

null값을 허용한 필드에서만 삭제가 가능하다.

# drink 테이블에서 id값이 1인 데이터의 size 값만 삭제
Drink.objects.filter(id=1).update(size=None)

# 결과값
id          name         category_id  size_id     nutrition_id
----------  -----------  -----------  ----------  ------------
1           나이트로 바닐라 크림  1                        1
2           나이트로 쇼콜라 클라  1            1           2
3           아이스 커피       2            1           3

번외 - 삭제한 열과 동일한 id값으로 다시 데이터 저장하기

size 테이블의 첫 번째 열(id값 1)을 삭제한 뒤 다시 id값이 1인 데이터를 입력하는 방법이다. 위에서 썼던 filterupdate로 입력하려고 했으나 id=1인 데이터 자체가 없으므로 필터에서 걸리지 않아 실패. 아예 size 테이블에 id=1인 열을 만들어주고 데이터를 밀어넣었다.

# size 테이블에서 id값이 1인 열 전체 삭제
>>> Size.objects.filter(id=1).delete()

# 사이즈 테이블에 id값이 1인 열 생성
>>> Size().id = 1
>>> Size().save()

# 사이즈 테이블에서 id값이 1인 열에 데이터 저장
>>> Size.objects.filter(id=1).update(name='Tall(톨)', size_ml=355, size_oz=12)

어려웠던 점

Aquery로 구조를 미리 짜놓은 덕분에 models.py에서 클래스 생성하는 것은 어렵지 않았다. 다만 데이터를 신규로 입력할 때 fk 부분에서 많이 헤맸다. Query set 형태 말고 객체 형태로 넣어줘야 한다는 것을 수 번 시행착오를 겪은 뒤에 깨달았다.