앱이란?
- 장고 프로젝트는 여러 개의 앱으로 구성되어 있는데, 앱이란 웹사이트를 기능별로 구분해놓은 것이다.
- 예를 들어, 홈페이지에서 상품, 결제, 채팅 등의 기능들을 앱으로 구성하는 것이다.
starbucks
프로젝트 아래에 products
라는 앱을 생성한다.
cd starbucks
python manage.py startapp products
앱을 생성하면 다음과 같은 파일들이 자동으로 만들어진다.
앱을 생성하면 starbucks/settings.py
에 들어가 앱 등록을 해주어야 한다. 그래야 테이블 생성이 가능하다. INSTALLED_APPS
리스트에 products
를 추가한다.
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'products',
]
모델이란?
- 장고에서 모델(model)은 데이터를 관리하는 곳이다.
- 모델은 각 앱 안의 models.py 모듈에서 정의하게 된다.
- models.py 모듈 안에 하나 이상의 모델 클래스를 정의할 수 있으며, 하나의 모델 클래스는 데이타베이스에서 하나의 테이블에 해당된다.
스타벅스 홈페이지의 상품 모델링을 기준으로 장고 모델을 작성해보겠다.
테이블 간 관계가 눈에 잘 보이도록 간단하게 도식화해보았다.
이 관계를 바탕으로 products/models.py
에서 내가 작성한 코드는 아래와 같다.
from django.db import models
class Menu(models.Model):
name = models.CharField(max_length=50)
class Meta:
db_table = 'menus'
class Category(models.Model):
name = models.CharField(max_length=50)
menu = models.ForeignKey('Menu', on_delete=models.CASCADE)
class Meta:
db_table = 'categories'
class Product(models.Model):
korean_name = models.CharField(max_length=100)
english_name = models.CharField(max_length=100)
description = models.CharField(max_length=300)
is_new = models.BooleanField(default=False)
category = models.ForeignKey('Category', on_delete=models.CASCADE)
class Meta:
db_table = 'products'
class Image(models.Model):
image_url = models.URLField(max_length=2000)
product = models.ForeignKey('Product', on_delete=models.CASCADE)
class Meta:
db_table = 'images'
class Allergy(models.Model):
name = models.CharField(max_length=30)
products = models.ManyToManyField('Product', through='AllergyProduct')
class Meta:
db_table = 'allergies'
class AllergyProduct(models.Model):
allergy = models.ForeignKey('Allergy', on_delete=models.CASCADE)
product = models.ForeignKey('Product', on_delete=models.CASCADE)
class Meta:
db_table = 'allergies_products'
class Nutrition(models.Model):
one_serving_kcal = models.DecimalField(max_digits=5, decimal_places=1, null=True)
sodium_mg = models.DecimalField(max_digits=5, decimal_places=1, null=True)
saturated_fat_g = models.DecimalField(max_digits=5, decimal_places=1, null=True)
sugars_g = models.DecimalField(max_digits=5, decimal_places=1, null=True)
protein_g = models.DecimalField(max_digits=5, decimal_places=1, null=True)
caffeine_mg = models.DecimalField(max_digits=5, decimal_places=1, null=True)
size = models.CharField(max_length=30)
product = models.OneToOneField('Product', on_delete=models.CASCADE)
class Meta:
db_table = 'nutritions'
CharField
: 글자 수가 제한된 텍스트를 정의할 때 사용, max_length
지정URLField
: URL을 입력해야 할 때는 CharField 대신 사용, max_length
지정DecimalField
: max_digits
에는 소수점 아래를 포함하여 전체 길이, decimal_places
에는 소수점 아래의 길이를 지정 BooleanField
: true/false 필드, 0 또는 1로 입력 가능장고에서는 이것말고도 많은 속성이 존재하는데, 더 알고 싶다면 장고 속성 곰식문서를 참고하자.
id
를 작성하지 않았다. pk가 지정되어 있지 않으면 장고에서 알아서 id 컬럼을 만들고 pk를 생성한다._id
가 붙었으나, 장고에서는 빼고 작성한다. 장고에서 알아서 _id
를 붙여 데이터베이스에 넘기기 때문이다. # menu(O) menu_id(X)
menu = models.ForeignKey('Menu', on_delete=models.CASCADE)
menu_id=1
처럼 _id
를 명시해야 한다. 데이터베이스에 테이블명을 클래스명이 아닌 다른 이름으로 짓고 싶다면 Meta
class를 사용한다. 아래의 예시에서 클래스명은 Menu
이지만 데이터베이스에는 menus
라는 테이블로 저장된다.
class Menu(models.Model):
name = models.CharField(max_length=50)
class Meta:
db_table = 'menus'
ForeignKey
의 on_delete
속성은 참조하고 있는 테이블이 삭제가 되면 어떻게 할 것인지 결정한다. CASCADE
: 상위 테이블의 데이터가 삭제되면 연결되어 있던 데이터도 삭제된다.SET_NULL
: 외래키 지정되었던 컬럼만 null로 수정되고 나머지 데이터는 그대로 있다. null=True
를 꼭 해줘야 한다.product = models.ForeignKey('Product', on_delete=models.CASCADE)
product = models.ForeignKey('Product', on_delete=models.SET_NTLL, null=True)
Product
와 Nutrition
의 관계
한 쪽 테이블에 OneToOneField
를 사용해 관계를 설정한다.
class Nutrition(models.Model):
product = models.OneToOneField('Product', on_delete=models.CASCADE)
Menu
(1)와 Category
(N)의 관계Category
(1)와 Product
(N)의 관계Product
(1)와 Image
(N)의 관계N
인 테이블이 1
인 테이블의 id를 ForeignKey
로 가지면 된다.class Category(models.Model):
menu = models.ForeignKey('Menu', on_delete=models.CASCADE)
Allergy
와 Product
의 관계1. 중간테이블이 없는 경우
ManyToManyField
를 사용해 관계를 설정한다.products
처럼 자유롭게 지정할 수 있다. class Allergy(models.Model):
products = models.ManyToManyField('Product')
2. 중간테이블이 있는 경우
ManyToManyField
를 사용하는데,through='AllergyProduct'
로 중간테이블과 연결해준다. Allergy
와 Product
참조하는 ForeignKey를 하나씩 만들어준다.class Allergy(models.Model):
products = models.ManyToManyField('Product', through='AllergyProduct')
class AllergyProduct(models.Model):
allergy = models.ForeignKey('Allergy', on_delete=models.CASCADE)
product = models.ForeignKey('Product', on_delete=models.CASCADE)
중간테이블을 왜 만들까?
중간 테이블 클래스를 모델에 직접 정의지 않으면 두 테이블 id의 조합 외에 다른 값을 넣기가 힘들다. 따라서 중간 테이블을 만들어서 사용하는 것이 컨트롤하기 좋다.
migrations는 장고에서 모델에 대한 변경사항을 데이터베이스 스키마에 전달하는 방법이다.
makemigrations
makemigrations
명령어는 모델의 변경사항을 기준으로 새로운 migration을 생성한다. <app-name>/migrations/0001_initial.py
와 같이 파일로 저장된다.makemigrations
이후에 migrations 폴더를 확인하는 습관을 갖는 게 좋다. makemigrations [app-name]
와 같이 앱 네임을 함께 명시해둬여 예상치 못한 migration을 방지할 수 있다.migrate
migrate
명령어로 migrations을 적용하거나 적용 취소한다.sqlmigrate
sqlmigrate
로 실제 데이터베이스에 전달되는 SQL 쿼리문을 확인할 수 있다. migrate
하기 전에 확인하는 습관을 갖는 게 좋다. showmigrations
[x]
: migrate 적용 후 [ ]
: migrate 적용 전migrations는 데이터베이스 스키마의 버전 제어 시스템이라 할 수 있다. git과 비슷하게 생각하면 된다. makemigrations
는 commit과 유사하게 모델의 변경 사항을 migration 파일로 패키징하고, migrate
는 이를 데이터베이스에 적용하는 일을 담당한다.
더욱 자세한 정보는 Django migrations 공식 문서를 참고하자.
이제 실제로 migration 작업을 해보자!
먼저 makemigrations
으로 모델 변경사항을 장고에게 알려준다.
python manage.py makemigrations products
sqlmigrate
로 어떤 식으로 SQL 구문이 생성되는지 확인해주고,
python manage.py sqlmigrate products 0001
migrations
폴더 안에도 0001_initial.py
파일이 생겼다.
이제 migrate
로 migration을 데이터베이스에 적용한다.
python manage.py migrate products
showmigrations
로 현재 migrations가 어떤 상태인지 살펴보면
python manage.py showmigrations products
[X]
표시로 0001 initial
파일이 적용되었다는 것을 알 수 있다.
Mysql에서도 클래스와 연동된 테이블들이 생겨난 것을 확인할 수 있다. 테이블의 이름은 Meta 클래스에서 지정한 이름이다.