2편에 이어.. 이제 마지막 챕터이다.
데이터 CRUD 작업을 할건데, Shell을 켜도록 하자
터미널 환경에서 OS와 유저 사이를 이어주는 역할을 하는 프로그램이다.
참조를 이용하지 않는다는 것은, 외부 테이블의 pk값을 가져오는 게 없이
값을 저장한다는 의미이다.
해당 테이블 : menus, allergy
우선 models.py에서 데이터베이스에 접근하기 때문에,
해당 파일을 import 해준다.
그리고 우선 가장 상위 카테고리인 메뉴만 만들 예정이다.
from products.models import Menu, Allergy
#Menu, allergy 테이블 조회
Menu.obejects.all()
Allergy.objects.all()
#빈 쿼리 셋 출력
QuerySet[]
#Menu 데이터 입력
#스타벅스의 홈페이지가면 '음료', '보도자료', '푸드', '상품', '카드'가 있다.
Menu.objects.create(name='음료')
Menu.objects.create(name='보도자료')
Menu.objects.create(name='푸드'
Menu.objects.create(name='상품')
Menu.objects.create(name='카드')
#Menu 테이블 조회 use SQL
select * from menus;
#Menu 테이블 조회 use ORM
Menu.objects.all()
#쿼리셋 출력(괄호안의 숫자는 id값이다)
<QuerySet [<Menu: Menu object (1)>, <Menu: Menu object (2)>, <Menu: Menu object (3)>, <Menu: Menu object (4)>, <Menu: Menu object (5)>]>
메뉴와 카테고리를 보면,
메뉴에서 음료를 눌러야 음료 카테고리가 나온다
그러므로 카테고리는 메뉴의 PK값을 참조하고 있다.
그리고 음료와 카테고리를 OneToMany로 설정했는데,
메뉴를 '음료'로 선택하면 선택할 수 있는 카테고리는 10개
그러면 카테고리 테이블의 menu_id는 메뉴 테이블에서 불러와야 한다.
이럴 때, 메뉴의 키값이자 카테고리 테이블의 참조키를 변수에 넣어줘서 가져온다.
#귀찮아서 그냥 다 import
from products.models import *
#메뉴에서 음료의 키값을 변수에 입력하기 위한 과정
m_id = Menu.objects.get(id=1)
m_id.save()
m_id
#결과
<Menu: Menu object (1)>
#카테고리에 Insert / 괄호안은 실제 DB의 컬럼명을 넣어준다
Category.objects.create(menu_id = m_id.id, name='콜드브루커피')
Category.objects.create(menu_id = m_id.id, name='브루드커피')
Category.objects.create(menu_id = m_id.id, name='에스프레스')
Category.objects.create(menu_id = m_id.id, name='푸라푸치노')
Category.objects.create(menu_id = m_id.id, name='블렌디드')
menu_id = 메뉴 중 음료의 키 값이므로 이렇게 참조되어 들어가진다
이렇게 해서 나머지 테이블에 값을 넣어준다
데이터를 조회하기 위한 QuerySet은 어떤 게 있을까?
>> Product.objects.all()
조회 시, id가 1~5인 5개의 데이터가 있음을 알 수 있다.
이건 쿼리 조회 결과
이 데이터 중 내가 얻고 싶은 특정 데이터를 지목할 때 사용한다.
>> Product.object.get(id=2)
get()의 가장 큰 특징은 데이터를 단 하나의 행만 반환한다는 것!
get(id=2)나 get(kor_name__contains='나이트')를 했을 때
get() returned more than one Product -- it returned 3!
당신이 사용한 get() 메소드의 결과는 1개 이상의 값이 리턴되었다라고 말한 뒤,
몇 개 리턴하는지까지 친절히 써주는 것도 볼 수 있다.
get()이 1개만을 반환하면, 이걸 보완하기 위한 것이 filter()이다.
>> Product.objects.filter(kor_name__contains='나이트')
그러면 id=1,2,5인 쿼리셋이 나오는 게 확인된다
테이블 조회 결과, 한국 이름에 '나이트'가 음료는 1,2,5번 확인
내가 조회 할 때, 돌체콜드브루를 제외하고 찾고 싶다면?
이 때 사용하는 것이 exclude() 이다.
# 조회조건은 간편하게 id로 함(kor_name='돌체콜드브루' 이렇게 해도 됨)
>> Product.objects.exclude(id=3)
나는 처음에 이거랑 filter()랑 대조돼서 포함하지 않는 것만 나오는 줄 알았는데
그게 아니었다.
엄연히 따지면 get ≒ exclude 인 셈..
찾아보니 and, or, not 은 Q객체를 사용한다고 하는데 그 때 배울듯;
객체의 값을 볼 때 사용한다.
보다시피 형태는 딕셔너리이며 전체 값을 볼 수도 있고, 필터링도 가능
# 전체 값 추출
>> Product.objects.values()
# 전체 값 중 필터링
>> Product.objects.values().filter(id_lt=3)
values()를 통해 전체 값 보기
전체 값 중 원하는 거 필터링
values() 함수와 달리 튜플로 값을 반환한다.
#전체 데이터 추출
>> Product.objects.values_list()
#전체 데이터 중 id<3인것만 추출
>> Product.objects.values_list()
#전체 데이터 중 한글이름이 '나이트'가 들어가는 음료의 한글명만 추출
>> Product.objects.filter(kor_name__contais('나이트').values_list('kor_name')
>> for category in Category.objects.filter(menu__name='음료') :
.... print(category.name)
....
마지막에 엔터를 한 번 더 눌러야 실행된다;;
menu__name에 대해 추가 설명
이전에 쿼리셋을 작성할 때는 아래의 테이블 필드명을 이용해서 사용했었다.
그런데 해당 반복문은 파이썬의 문법이므로,
파이썬 models.py에서 작성한 걸 기준으로 해야한다.
menu는 Menu 클래스의 pk를 참조하기 때문에,
메뉴 테이블에서 id가 n인 것의 이름을 가져오게 되는 것이다.
음료 데이터에 5개가 있는데, 돌체콜드브루에 대한 변경사항이 생겼다
이름 : 돌체콜드브루 -> 따뜻한콜드브루
설명 : 무더운 여름철 -> 신규 메뉴로 재런칭되었습니다
#이름과 설명 변경
>> Product.objects.filter(name__contains='돌체').update(kor_name='따뜻한콜드브루', desc='신규메뉴로 재런칭되었습니다')
>> 1 #변경된 개수가 아웃풋으로 나옴
그리고 메뉴에 대한 정보가 바뀜에 따라,
영양정보 또한 일부 조정될 예정이다.
바뀐 따뜻한콜드브루의 영양정보 값은 3번이었다.
그런데, 담당자의 실수로 3번을 바꾸면 되는 걸 3번을 삭제하고 6번으로 변경해버렸다.
이 때 담당자가 한 실수 과정은 다음과 같다.
1. 영양정보 테이블에 신규 행 삽입(id=6)
- 해당 테이블에서 3번 삭제
>> Nut.objects.filter(id=3).delete()
(3, {'products.allergyProducts': 1, 'products.Product': 1, 'products.Nut': 1})
그러면 음료/알러지/영양정보에서 한 개씩 삭제되어 3개가 삭제됐다고 나온다.
왜 그런가 봤더니
class Nut(models.Model) :
one_serving_kcal = models.DecimalField(max_digits=6,decimal_places=2)
sodium_mg = models.DecimalField(max_digits=6,decimal_places=2)
saturated_fat_g = models.DecimalField(max_digits=6,decimal_places=2)
sugars_g = models.DecimalField(max_digits=6,decimal_places=2)
protein_g = models.DecimalField(max_digits=6,decimal_places=2)
caffeine_mg = models.DecimalField(max_digits=6,decimal_places=2)
size_ml = models.CharField(max_length=20)
size_fluid_ounce = models.CharField(max_length=20)
class Product(models.Model) :
category = models.ForeignKey('Category', on_delete=models.CASCADE)
kor_name = models.CharField(max_length=20)
eng_name = models.CharField(max_length=20)
desc = models.TextField()
nutr = models.OneToOneField('Nut', on_delete=models.CASCADE)
class allergyProducts(models.Model) :
allergy = models.OneToOneField('Allergy', on_delete=models.CASCADE)
product = models.OneToOneField('Product', on_delete=models.CASCADE)
영양정보의 PK값을 음료에서 1대1로 참조하여,
영양정보가 삭제되면 음료의 영양정보가 삭제되는데,
그랬더니 음료의 그 행이 삭제가 됐다.
그런데 음료의 PK값을 알러지가 가져오니 얘도 삭제됨;;
이 세개의 특징이 모두 1대1 관계로 이루어졌다는 점이다.
그러면 수정할 값을 미리 수정해주고 그 때 없애야 한다는 걸 깨달았다
그래서 음료와 영양정보에 한 개씩 추가했고,
다음과 같이 작업을 해볼 예정이다.
1. id=6인 음료의 nutr_id = 7로 변경
2. nutrtions에서 id=6 인 것 삭제
get()으로 수정하면 에러가 나고, filter()로 수정하면 된다.
이게 왜 그런가 찾아보니
get()으로 받아와서 업데이트 하는 건, pk가 단일컬럼으로 잡혀있는경우만 가능합니다
라고 하는데, 이게 무슨 말이냐면
products 테이블은 얼핏 보면, PK가 id하나 같지만 nutr_id가 Unique key라서
얘도 PK범주에 들어서 수정이 안되는 것이다.
여튼 수정 후 nutrition 테이블에서 id=6인 걸 삭제한 뒤 조회해보면
products 테이블에 관계없이, nutrition에서만 삭제된 걸 알 수 있다.
음료와 이미지는가 이 관계에 해당된다.
현재 image 테이블에는 1번 음료의 이미지가 들어가있다.
#이미지 테이블 데이터 삭제
>>> Images.objects.filter(product_id=1).delete()
그 결과 음료 테이블의 데이터만 삭제된 걸 알 수 있다.
관계 설정에 따른 데이터 변경 시, 잘 확인해봐야 겠다.